From d758a3d5fd61a81677ae9f053c4c300e80f0aa92 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 21 Aug 2022 17:51:38 +0200 Subject: [PATCH 001/189] README: Add chapter "State of this library" (#490) Related #474 --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 571d4c3..ed9a6d0 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,22 @@ ![Go client library for Atlassian Jira](./img/logo_small.png "Go client library for Atlassian Jira.") +## State of this library + +v2 of this library is in development. +The goals of v2 are: + +* idiomatic go usage +* proper documentation +* being compliant with different kinds of Atlassian Jira products (on-premise vs. cloud) +* remove flaws introduced during the early times of this library + +See our milestone [Road to v2](https://github.com/andygrunwald/go-jira/milestone/1) and provide feedback in [Development is kicking: Road to v2 🚀 #489](https://github.com/andygrunwald/go-jira/issues/489). +Attention: The current `main` branch represents the v2 development version - we treat this version as unstable and breaking changes are expected. + +If you want to stay more stable, please use v1.* - See our [releases](https://github.com/andygrunwald/go-jira/releases). +Latest stable release: [v1.16.0](https://github.com/andygrunwald/go-jira/releases/tag/v1.16.0) + ## Features * Authentication (HTTP Basic, OAuth, Session Cookie) From 942f122b4e558b8fbd83a3b21f560a9a5f0765e8 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 21 Aug 2022 17:53:30 +0200 Subject: [PATCH 002/189] Go: Raise supported version to v1.18 (#491) * README: Add chapter "State of this library" Related #474 * README: Add chapter "Supported Go versions" Related #290 * Go: Raise supported version to v1.18 Related #290 * Fix "package io/ioutil is deprecated: As of Go 1.16, the same functionality is now provided by package io or package os, and those implementations should be preferred in new code. See the specific function documentation for details. (SA1019)" * go fmt --- .github/workflows/testing.yml | 2 +- README.md | 6 +++--- authentication.go | 4 ++-- authentication_test.go | 14 +++++++------- board_test.go | 12 ++++++------ error.go | 4 ++-- examples/addlabel/main.go | 4 ++-- field_test.go | 4 ++-- filter_test.go | 12 ++++++------ go.mod | 5 +++-- issue.go | 23 ++++++++++++----------- issue_test.go | 10 +++++----- issuelinktype.go | 4 ++-- issuelinktype_test.go | 4 ++-- jira.go | 5 +++-- jira_test.go | 8 ++++---- metaissue.go | 28 +++++++++++++++------------- permissionschemes_test.go | 10 +++++----- priority_test.go | 4 ++-- project_test.go | 8 ++++---- resolution_test.go | 4 ++-- role_test.go | 10 +++++----- servicedesk.go | 5 ++--- sprint.go | 2 +- sprint_test.go | 4 ++-- status_test.go | 4 ++-- statuscategory_test.go | 4 ++-- user.go | 4 ++-- version.go | 4 ++-- 29 files changed, 108 insertions(+), 104 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 225b0b8..bda0521 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - go: [ '1.18', '1.17' ] + go: [ '1.19', '1.18' ] os: [ 'windows-latest', 'ubuntu-latest', 'macOS-latest' ] runs-on: ${{ matrix.os }} diff --git a/README.md b/README.md index ed9a6d0..8a74e8b 100644 --- a/README.md +++ b/README.md @@ -317,11 +317,11 @@ A few examples: If you are new to pull requests, checkout [Collaborating on projects using issues and pull requests / Creating a pull request](https://help.github.com/articles/creating-a-pull-request/). -### Dependency management +### Supported Go versions -`go-jira` uses `go modules` for dependency management. After cloning the repo, it's easy to make sure you have the correct dependencies by running `go mod tidy`. +We follow the [Go Release Policy](https://go.dev/doc/devel/release#policy): -For adding new dependencies, updating dependencies, and other operations, the [Daily workflow](https://github.com/golang/go/wiki/Modules#daily-workflow) is a good place to start. +> Each major Go release is supported until there are two newer major releases. For example, Go 1.5 was supported until the Go 1.7 release, and Go 1.6 was supported until the Go 1.8 release. We fix critical problems, including [critical security problems](https://go.dev/security), in supported releases as needed by issuing minor revisions (for example, Go 1.6.1, Go 1.6.2, and so on). ### Sandbox environment for testing diff --git a/authentication.go b/authentication.go index fcaf8e1..2f41f8c 100644 --- a/authentication.go +++ b/authentication.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" ) @@ -188,7 +188,7 @@ func (s *AuthenticationService) GetCurrentUserWithContext(ctx context.Context) ( return nil, fmt.Errorf("getting user info failed with status : %d", resp.StatusCode) } ret := new(Session) - data, err := ioutil.ReadAll(resp.Body) + data, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("couldn't read body from the response : %s", err) } diff --git a/authentication_test.go b/authentication_test.go index 9235d08..0ceea89 100644 --- a/authentication_test.go +++ b/authentication_test.go @@ -3,7 +3,7 @@ package jira import ( "bytes" "fmt" - "io/ioutil" + "io" "net/http" "reflect" "testing" @@ -15,7 +15,7 @@ func TestAuthenticationService_AcquireSessionCookie_Failure(t *testing.T) { testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/auth/1/session") - b, err := ioutil.ReadAll(r.Body) + b, err := io.ReadAll(r.Body) if err != nil { t.Errorf("Error in read body: %s", err) } @@ -49,7 +49,7 @@ func TestAuthenticationService_AcquireSessionCookie_Success(t *testing.T) { testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/auth/1/session") - b, err := ioutil.ReadAll(r.Body) + b, err := io.ReadAll(r.Body) if err != nil { t.Errorf("Error in read body: %s", err) } @@ -140,7 +140,7 @@ func TestAuthenticationService_GetUserInfo_AccessForbidden_Fail(t *testing.T) { if r.Method == "POST" { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/auth/1/session") - b, err := ioutil.ReadAll(r.Body) + b, err := io.ReadAll(r.Body) if err != nil { t.Errorf("Error in read body: %s", err) } @@ -178,7 +178,7 @@ func TestAuthenticationService_GetUserInfo_NonOkStatusCode_Fail(t *testing.T) { if r.Method == "POST" { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/auth/1/session") - b, err := ioutil.ReadAll(r.Body) + b, err := io.ReadAll(r.Body) if err != nil { t.Errorf("Error in read body: %s", err) } @@ -234,7 +234,7 @@ func TestAuthenticationService_GetUserInfo_Success(t *testing.T) { if r.Method == "POST" { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/auth/1/session") - b, err := ioutil.ReadAll(r.Body) + b, err := io.ReadAll(r.Body) if err != nil { t.Errorf("Error in read body: %s", err) } @@ -276,7 +276,7 @@ func TestAuthenticationService_Logout_Success(t *testing.T) { if r.Method == "POST" { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/auth/1/session") - b, err := ioutil.ReadAll(r.Body) + b, err := io.ReadAll(r.Body) if err != nil { t.Errorf("Error in read body: %s", err) } diff --git a/board_test.go b/board_test.go index 11a5dc2..0806b76 100644 --- a/board_test.go +++ b/board_test.go @@ -2,8 +2,8 @@ package jira import ( "fmt" - "io/ioutil" "net/http" + "os" "testing" ) @@ -12,7 +12,7 @@ func TestBoardService_GetAllBoards(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/agile/1.0/board" - raw, err := ioutil.ReadFile("./mocks/all_boards.json") + raw, err := os.ReadFile("./mocks/all_boards.json") if err != nil { t.Error(err.Error()) } @@ -37,7 +37,7 @@ func TestBoardService_GetAllBoards_WithFilter(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/agile/1.0/board" - raw, err := ioutil.ReadFile("./mocks/all_boards_filtered.json") + raw, err := os.ReadFile("./mocks/all_boards_filtered.json") if err != nil { t.Error(err.Error()) } @@ -160,7 +160,7 @@ func TestBoardService_GetAllSprints(t *testing.T) { testAPIEndpoint := "/rest/agile/1.0/board/123/sprint" - raw, err := ioutil.ReadFile("./mocks/sprints.json") + raw, err := os.ReadFile("./mocks/sprints.json") if err != nil { t.Error(err.Error()) } @@ -192,7 +192,7 @@ func TestBoardService_GetAllSprintsWithOptions(t *testing.T) { testAPIEndpoint := "/rest/agile/1.0/board/123/sprint" - raw, err := ioutil.ReadFile("./mocks/sprints_filtered.json") + raw, err := os.ReadFile("./mocks/sprints_filtered.json") if err != nil { t.Error(err.Error()) } @@ -223,7 +223,7 @@ func TestBoardService_GetBoardConfigoration(t *testing.T) { defer teardown() testAPIEndpoint := "/rest/agile/1.0/board/35/configuration" - raw, err := ioutil.ReadFile("./mocks/board_configuration.json") + raw, err := os.ReadFile("./mocks/board_configuration.json") if err != nil { t.Error(err.Error()) } diff --git a/error.go b/error.go index c7bc2e5..c049642 100644 --- a/error.go +++ b/error.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" + "io" "strings" "github.com/pkg/errors" @@ -25,7 +25,7 @@ func NewJiraError(resp *Response, httpError error) error { } defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return errors.Wrap(err, httpError.Error()) } diff --git a/examples/addlabel/main.go b/examples/addlabel/main.go index 7c1de67..3b0e82d 100644 --- a/examples/addlabel/main.go +++ b/examples/addlabel/main.go @@ -3,7 +3,7 @@ package main import ( "bufio" "fmt" - "io/ioutil" + "io" "os" "strings" "syscall" @@ -67,7 +67,7 @@ func main() { if err != nil { fmt.Println(err) } - body, _ := ioutil.ReadAll(resp.Body) + body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) issue, _, _ := client.Issue.Get(issueId, nil) diff --git a/field_test.go b/field_test.go index a2deb53..9bdf361 100644 --- a/field_test.go +++ b/field_test.go @@ -2,8 +2,8 @@ package jira import ( "fmt" - "io/ioutil" "net/http" + "os" "testing" ) @@ -12,7 +12,7 @@ func TestFieldService_GetList(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/field" - raw, err := ioutil.ReadFile("./mocks/all_fields.json") + raw, err := os.ReadFile("./mocks/all_fields.json") if err != nil { t.Error(err.Error()) } diff --git a/filter_test.go b/filter_test.go index 1b3a690..5d19fc3 100644 --- a/filter_test.go +++ b/filter_test.go @@ -2,8 +2,8 @@ package jira import ( "fmt" - "io/ioutil" "net/http" + "os" "testing" ) @@ -11,7 +11,7 @@ func TestFilterService_GetList(t *testing.T) { setup() defer teardown() testAPIEndpoint := "/rest/api/2/filter" - raw, err := ioutil.ReadFile("./mocks/all_filters.json") + raw, err := os.ReadFile("./mocks/all_filters.json") if err != nil { t.Error(err.Error()) } @@ -34,7 +34,7 @@ func TestFilterService_Get(t *testing.T) { setup() defer teardown() testAPIEndpoint := "/rest/api/2/filter/10000" - raw, err := ioutil.ReadFile("./mocks/filter.json") + raw, err := os.ReadFile("./mocks/filter.json") if err != nil { t.Error(err.Error()) } @@ -58,7 +58,7 @@ func TestFilterService_GetFavouriteList(t *testing.T) { setup() defer teardown() testAPIEndpoint := "/rest/api/2/filter/favourite" - raw, err := ioutil.ReadFile("./mocks/favourite_filters.json") + raw, err := os.ReadFile("./mocks/favourite_filters.json") if err != nil { t.Error(err.Error()) } @@ -81,7 +81,7 @@ func TestFilterService_GetMyFilters(t *testing.T) { setup() defer teardown() testAPIEndpoint := "/rest/api/3/filter/my" - raw, err := ioutil.ReadFile("./mocks/my_filters.json") + raw, err := os.ReadFile("./mocks/my_filters.json") if err != nil { t.Error(err.Error()) } @@ -105,7 +105,7 @@ func TestFilterService_Search(t *testing.T) { setup() defer teardown() testAPIEndpoint := "/rest/api/3/filter/search" - raw, err := ioutil.ReadFile("./mocks/search_filters.json") + raw, err := os.ReadFile("./mocks/search_filters.json") if err != nil { t.Error(err.Error()) } diff --git a/go.mod b/go.mod index 21eb225..0a13bf0 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/andygrunwald/go-jira -go 1.15 +go 1.18 require ( github.com/fatih/structs v1.1.0 @@ -9,6 +9,7 @@ require ( github.com/google/go-querystring v1.1.0 github.com/pkg/errors v0.9.1 github.com/trivago/tgo v1.0.7 - golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f // indirect golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d ) + +require golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f // indirect diff --git a/issue.go b/issue.go index 0aa03b7..6cb6781 100644 --- a/issue.go +++ b/issue.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "mime/multipart" "net/http" "net/url" @@ -613,7 +612,7 @@ type RemoteLinkStatus struct { // This can be an issue id, or an issue key. // If the issue cannot be found via an exact match, Jira will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved. // -// The given options will be appended to the query string +// # The given options will be appended to the query string // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getIssue func (s *IssueService) GetWithContext(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { @@ -829,7 +828,7 @@ func (s *IssueService) CreateWithContext(ctx context.Context, issue *Issue) (*Is responseIssue := new(Issue) defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) + data, err := io.ReadAll(resp.Body) if err != nil { return nil, resp, fmt.Errorf("could not read the returned data") } @@ -1295,15 +1294,17 @@ func (s *IssueService) DoTransitionWithPayload(ticketID, payload interface{}) (* } // InitIssueWithMetaAndFields returns Issue with with values from fieldsConfig properly set. -// * metaProject should contain metaInformation about the project where the issue should be created. -// * metaIssuetype is the MetaInformation about the Issuetype that needs to be created. -// * fieldsConfig is a key->value pair where key represents the name of the field as seen in the UI -// And value is the string value for that particular key. +// - metaProject should contain metaInformation about the project where the issue should be created. +// - metaIssuetype is the MetaInformation about the Issuetype that needs to be created. +// - fieldsConfig is a key->value pair where key represents the name of the field as seen in the UI +// And value is the string value for that particular key. +// // Note: This method doesn't verify that the fieldsConfig is complete with mandatory fields. The fieldsConfig is -// supposed to be already verified with MetaIssueType.CheckCompleteAndAvailable. It will however return -// error if the key is not found. -// All values will be packed into Unknowns. This is much convenient. If the struct fields needs to be -// configured as well, marshalling and unmarshalling will set the proper fields. +// +// supposed to be already verified with MetaIssueType.CheckCompleteAndAvailable. It will however return +// error if the key is not found. +// All values will be packed into Unknowns. This is much convenient. If the struct fields needs to be +// configured as well, marshalling and unmarshalling will set the proper fields. func InitIssueWithMetaAndFields(metaProject *MetaProject, metaIssuetype *MetaIssueType, fieldsConfig map[string]string) (*Issue, error) { issue := new(Issue) issueFields := new(IssueFields) diff --git a/issue_test.go b/issue_test.go index ceeff9a..a867535 100644 --- a/issue_test.go +++ b/issue_test.go @@ -4,8 +4,8 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" + "os" "reflect" "strings" "testing" @@ -418,7 +418,7 @@ func TestIssueService_DownloadAttachment(t *testing.T) { } defer resp.Body.Close() - attachment, err := ioutil.ReadAll(resp.Body) + attachment, err := io.ReadAll(resp.Body) if err != nil { t.Error("Expected attachment text", err) } @@ -477,7 +477,7 @@ func TestIssueService_PostAttachment(t *testing.T) { status = http.StatusNoContent } else { // Read the file into memory - data, err := ioutil.ReadAll(file) + data, err := io.ReadAll(file) if err != nil { status = http.StatusInternalServerError } @@ -823,7 +823,7 @@ func TestIssueService_GetTransitions(t *testing.T) { testAPIEndpoint := "/rest/api/2/issue/123/transitions" - raw, err := ioutil.ReadFile("./mocks/transitions.json") + raw, err := os.ReadFile("./mocks/transitions.json") if err != nil { t.Error(err.Error()) } @@ -1787,7 +1787,7 @@ func TestIssueService_GetRemoteLinks(t *testing.T) { testAPIEndpoint := "/rest/api/2/issue/123/remotelink" - raw, err := ioutil.ReadFile("./mocks/remote_links.json") + raw, err := os.ReadFile("./mocks/remote_links.json") if err != nil { t.Error(err.Error()) } diff --git a/issuelinktype.go b/issuelinktype.go index 24a3ab0..a9f7af2 100644 --- a/issuelinktype.go +++ b/issuelinktype.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" + "io" ) // IssueLinkTypeService handles issue link types for the Jira instance / API. @@ -77,7 +77,7 @@ func (s *IssueLinkTypeService) CreateWithContext(ctx context.Context, linkType * responseLinkType := new(IssueLinkType) defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) + data, err := io.ReadAll(resp.Body) if err != nil { e := fmt.Errorf("could not read the returned data") return nil, resp, NewJiraError(resp, e) diff --git a/issuelinktype_test.go b/issuelinktype_test.go index 5678662..8e01d83 100644 --- a/issuelinktype_test.go +++ b/issuelinktype_test.go @@ -2,8 +2,8 @@ package jira import ( "fmt" - "io/ioutil" "net/http" + "os" "testing" ) @@ -12,7 +12,7 @@ func TestIssueLinkTypeService_GetList(t *testing.T) { defer teardown() testAPIEndpoint := "/rest/api/2/issueLinkType" - raw, err := ioutil.ReadFile("./mocks/all_issuelinktypes.json") + raw, err := os.ReadFile("./mocks/all_issuelinktypes.json") if err != nil { t.Error(err.Error()) } diff --git a/jira.go b/jira.go index 25c52ba..c86a308 100644 --- a/jira.go +++ b/jira.go @@ -561,8 +561,9 @@ func (t *CookieAuthTransport) transport() http.RoundTripper { // // Jira docs: https://developer.atlassian.com/cloud/jira/platform/understanding-jwt // Examples in other languages: -// https://bitbucket.org/atlassian/atlassian-jwt-ruby/src/d44a8e7a4649e4f23edaa784402655fda7c816ea/lib/atlassian/jwt.rb -// https://bitbucket.org/atlassian/atlassian-jwt-py/src/master/atlassian_jwt/url_utils.py +// +// https://bitbucket.org/atlassian/atlassian-jwt-ruby/src/d44a8e7a4649e4f23edaa784402655fda7c816ea/lib/atlassian/jwt.rb +// https://bitbucket.org/atlassian/atlassian-jwt-py/src/master/atlassian_jwt/url_utils.py type JWTAuthTransport struct { Secret []byte Issuer string diff --git a/jira_test.go b/jira_test.go index 3aedbd6..280bfc7 100644 --- a/jira_test.go +++ b/jira_test.go @@ -3,7 +3,7 @@ package jira import ( "bytes" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httptest" "net/url" @@ -172,7 +172,7 @@ func TestClient_NewRequest(t *testing.T) { } // Test that body was JSON encoded - body, _ := ioutil.ReadAll(req.Body) + body, _ := io.ReadAll(req.Body) if got, want := string(body), outBody; got != want { t.Errorf("NewRequest(%v) Body is %v, want %v", inBody, got, want) } @@ -196,7 +196,7 @@ func TestClient_NewRawRequest(t *testing.T) { } // Test that body was JSON encoded - body, _ := ioutil.ReadAll(req.Body) + body, _ := io.ReadAll(req.Body) if got, want := string(body), outBody; got != want { t.Errorf("NewRawRequest(%v) Body is %v, want %v", inBody, got, want) } @@ -386,7 +386,7 @@ func TestClient_Do_HTTPResponse(t *testing.T) { req, _ := testClient.NewRequest("GET", "/", nil) res, _ := testClient.Do(req, nil) - _, err := ioutil.ReadAll(res.Body) + _, err := io.ReadAll(res.Body) if err != nil { t.Errorf("Error on parsing HTTP Response = %v", err.Error()) diff --git a/metaissue.go b/metaissue.go index 560a2f0..3953ac7 100644 --- a/metaissue.go +++ b/metaissue.go @@ -148,19 +148,21 @@ func (p *MetaProject) GetIssueTypeWithName(name string) *MetaIssueType { // GetMandatoryFields returns a map of all the required fields from the MetaIssueTypes. // if a field returned by the api was: -// "customfield_10806": { -// "required": true, -// "schema": { -// "type": "any", -// "custom": "com.pyxis.greenhopper.jira:gh-epic-link", -// "customId": 10806 -// }, -// "name": "Epic Link", -// "hasDefaultValue": false, -// "operations": [ -// "set" -// ] -// } +// +// "customfield_10806": { +// "required": true, +// "schema": { +// "type": "any", +// "custom": "com.pyxis.greenhopper.jira:gh-epic-link", +// "customId": 10806 +// }, +// "name": "Epic Link", +// "hasDefaultValue": false, +// "operations": [ +// "set" +// ] +// } +// // the returned map would have "Epic Link" as the key and "customfield_10806" as value. // This choice has been made so that the it is easier to generate the create api request later. func (t *MetaIssueType) GetMandatoryFields() (map[string]string, error) { diff --git a/permissionschemes_test.go b/permissionschemes_test.go index 8efc123..373e57a 100644 --- a/permissionschemes_test.go +++ b/permissionschemes_test.go @@ -2,8 +2,8 @@ package jira import ( "fmt" - "io/ioutil" "net/http" + "os" "testing" ) @@ -12,7 +12,7 @@ func TestPermissionSchemeService_GetList(t *testing.T) { defer teardown() testAPIEndpoint := "/rest/api/3/permissionscheme" - raw, err := ioutil.ReadFile("./mocks/all_permissionschemes.json") + raw, err := os.ReadFile("./mocks/all_permissionschemes.json") if err != nil { t.Error(err.Error()) } @@ -40,7 +40,7 @@ func TestPermissionSchemeService_GetList_NoList(t *testing.T) { defer teardown() testAPIEndpoint := "/rest/api/3/permissionscheme" - raw, err := ioutil.ReadFile("./mocks/no_permissionschemes.json") + raw, err := os.ReadFile("./mocks/no_permissionschemes.json") if err != nil { t.Error(err.Error()) } @@ -63,7 +63,7 @@ func TestPermissionSchemeService_Get(t *testing.T) { setup() defer teardown() testAPIEdpoint := "/rest/api/3/permissionscheme/10100" - raw, err := ioutil.ReadFile("./mocks/permissionscheme.json") + raw, err := os.ReadFile("./mocks/permissionscheme.json") if err != nil { t.Error(err.Error()) } @@ -86,7 +86,7 @@ func TestPermissionSchemeService_Get_NoScheme(t *testing.T) { setup() defer teardown() testAPIEdpoint := "/rest/api/3/permissionscheme/99999" - raw, err := ioutil.ReadFile("./mocks/no_permissionscheme.json") + raw, err := os.ReadFile("./mocks/no_permissionscheme.json") if err != nil { t.Error(err.Error()) } diff --git a/priority_test.go b/priority_test.go index b91a236..38258f0 100644 --- a/priority_test.go +++ b/priority_test.go @@ -2,8 +2,8 @@ package jira import ( "fmt" - "io/ioutil" "net/http" + "os" "testing" ) @@ -12,7 +12,7 @@ func TestPriorityService_GetList(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/priority" - raw, err := ioutil.ReadFile("./mocks/all_priorities.json") + raw, err := os.ReadFile("./mocks/all_priorities.json") if err != nil { t.Error(err.Error()) } diff --git a/project_test.go b/project_test.go index 6858b40..727267b 100644 --- a/project_test.go +++ b/project_test.go @@ -2,8 +2,8 @@ package jira import ( "fmt" - "io/ioutil" "net/http" + "os" "testing" ) @@ -12,7 +12,7 @@ func TestProjectService_GetList(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/project" - raw, err := ioutil.ReadFile("./mocks/all_projects.json") + raw, err := os.ReadFile("./mocks/all_projects.json") if err != nil { t.Error(err.Error()) } @@ -36,7 +36,7 @@ func TestProjectService_ListWithOptions(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/project" - raw, err := ioutil.ReadFile("./mocks/all_projects.json") + raw, err := os.ReadFile("./mocks/all_projects.json") if err != nil { t.Error(err.Error()) } @@ -60,7 +60,7 @@ func TestProjectService_Get(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/project/12310505" - raw, err := ioutil.ReadFile("./mocks/project.json") + raw, err := os.ReadFile("./mocks/project.json") if err != nil { t.Error(err.Error()) } diff --git a/resolution_test.go b/resolution_test.go index 7c01e75..c408631 100644 --- a/resolution_test.go +++ b/resolution_test.go @@ -2,8 +2,8 @@ package jira import ( "fmt" - "io/ioutil" "net/http" + "os" "testing" ) @@ -12,7 +12,7 @@ func TestResolutionService_GetList(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/resolution" - raw, err := ioutil.ReadFile("./mocks/all_resolutions.json") + raw, err := os.ReadFile("./mocks/all_resolutions.json") if err != nil { t.Error(err.Error()) } diff --git a/role_test.go b/role_test.go index 6e801fe..b1a63b6 100644 --- a/role_test.go +++ b/role_test.go @@ -2,8 +2,8 @@ package jira import ( "fmt" - "io/ioutil" "net/http" + "os" "testing" ) @@ -12,7 +12,7 @@ func TestRoleService_GetList_NoList(t *testing.T) { defer teardown() testAPIEndpoint := "/rest/api/3/role" - raw, err := ioutil.ReadFile("./mocks/no_roles.json") + raw, err := os.ReadFile("./mocks/no_roles.json") if err != nil { t.Error(err.Error()) } @@ -37,7 +37,7 @@ func TestRoleService_GetList(t *testing.T) { defer teardown() testAPIEndpoint := "/rest/api/3/role" - raw, err := ioutil.ReadFile("./mocks/all_roles.json") + raw, err := os.ReadFile("./mocks/all_roles.json") if err != nil { t.Error(err.Error()) } @@ -64,7 +64,7 @@ func TestRoleService_Get_NoRole(t *testing.T) { setup() defer teardown() testAPIEdpoint := "/rest/api/3/role/99999" - raw, err := ioutil.ReadFile("./mocks/no_role.json") + raw, err := os.ReadFile("./mocks/no_role.json") if err != nil { t.Error(err.Error()) } @@ -87,7 +87,7 @@ func TestRoleService_Get(t *testing.T) { setup() defer teardown() testAPIEdpoint := "/rest/api/3/role/10002" - raw, err := ioutil.ReadFile("./mocks/role.json") + raw, err := os.ReadFile("./mocks/role.json") if err != nil { t.Error(err.Error()) } diff --git a/servicedesk.go b/servicedesk.go index 7fbffc0..f877f86 100644 --- a/servicedesk.go +++ b/servicedesk.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "github.com/google/go-querystring/query" ) @@ -144,7 +143,7 @@ func (s *ServiceDeskService) AddCustomersWithContext(ctx context.Context, servic } defer resp.Body.Close() - _, _ = io.Copy(ioutil.Discard, resp.Body) + _, _ = io.Copy(io.Discard, resp.Body) return resp, nil } @@ -176,7 +175,7 @@ func (s *ServiceDeskService) RemoveCustomersWithContext(ctx context.Context, ser } defer resp.Body.Close() - _, _ = io.Copy(ioutil.Discard, resp.Body) + _, _ = io.Copy(io.Discard, resp.Body) return resp, nil } diff --git a/sprint.go b/sprint.go index f0f98d6..6d21e4e 100644 --- a/sprint.go +++ b/sprint.go @@ -86,7 +86,7 @@ func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, er // This can be an issue id, or an issue key. // If the issue cannot be found via an exact match, Jira will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved. // -// The given options will be appended to the query string +// # The given options will be appended to the query string // // Jira API docs: https://docs.atlassian.com/jira-software/REST/7.3.1/#agile/1.0/issue-getIssue // diff --git a/sprint_test.go b/sprint_test.go index 64125db..6c8b58a 100644 --- a/sprint_test.go +++ b/sprint_test.go @@ -3,8 +3,8 @@ package jira import ( "encoding/json" "fmt" - "io/ioutil" "net/http" + "os" "reflect" "testing" ) @@ -44,7 +44,7 @@ func TestSprintService_GetIssuesForSprint(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/agile/1.0/sprint/123/issue" - raw, err := ioutil.ReadFile("./mocks/issues_in_sprint.json") + raw, err := os.ReadFile("./mocks/issues_in_sprint.json") if err != nil { t.Error(err.Error()) } diff --git a/status_test.go b/status_test.go index 11a7534..19b1de8 100644 --- a/status_test.go +++ b/status_test.go @@ -2,8 +2,8 @@ package jira import ( "fmt" - "io/ioutil" "net/http" + "os" "testing" ) @@ -12,7 +12,7 @@ func TestStatusService_GetAllStatuses(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/status" - raw, err := ioutil.ReadFile("./mocks/all_statuses.json") + raw, err := os.ReadFile("./mocks/all_statuses.json") if err != nil { t.Error(err.Error()) } diff --git a/statuscategory_test.go b/statuscategory_test.go index 3f8b7ec..f58fc32 100644 --- a/statuscategory_test.go +++ b/statuscategory_test.go @@ -2,8 +2,8 @@ package jira import ( "fmt" - "io/ioutil" "net/http" + "os" "testing" ) @@ -12,7 +12,7 @@ func TestStatusCategoryService_GetList(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/statuscategory" - raw, err := ioutil.ReadFile("./mocks/all_statuscategories.json") + raw, err := os.ReadFile("./mocks/all_statuscategories.json") if err != nil { t.Error(err.Error()) } diff --git a/user.go b/user.go index 78d6039..078b82f 100644 --- a/user.go +++ b/user.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" + "io" ) // UserService handles users for the Jira instance / API. @@ -110,7 +110,7 @@ func (s *UserService) CreateWithContext(ctx context.Context, user *User) (*User, responseUser := new(User) defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) + data, err := io.ReadAll(resp.Body) if err != nil { e := fmt.Errorf("could not read the returned data") return nil, resp, NewJiraError(resp, e) diff --git a/version.go b/version.go index bca12a4..45eecd5 100644 --- a/version.go +++ b/version.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" + "io" ) // VersionService handles Versions for the Jira instance / API. @@ -68,7 +68,7 @@ func (s *VersionService) CreateWithContext(ctx context.Context, version *Version responseVersion := new(Version) defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) + data, err := io.ReadAll(resp.Body) if err != nil { e := fmt.Errorf("could not read the returned data") return nil, resp, NewJiraError(resp, e) From e3f4abd5b084b0ba78ed36b7da703008d3f3006d Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 21 Aug 2022 17:54:38 +0200 Subject: [PATCH 003/189] README: Add chapter "Supported Jira versions" and "Official Jira API documentation" (#492) * README: Add chapter "State of this library" Related #474 * README: Add chapter "Supported Go versions" Related #290 * Go: Raise supported version to v1.18 Related #290 * README: Add chapter "Supported Jira versions" and "Official Jira API documentation" Related: #295 * Fix "package io/ioutil is deprecated: As of Go 1.16, the same functionality is now provided by package io or package os, and those implementations should be preferred in new code. See the specific function documentation for details. (SA1019)" * go fmt --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 8a74e8b..a35998c 100644 --- a/README.md +++ b/README.md @@ -323,6 +323,26 @@ We follow the [Go Release Policy](https://go.dev/doc/devel/release#policy): > Each major Go release is supported until there are two newer major releases. For example, Go 1.5 was supported until the Go 1.7 release, and Go 1.6 was supported until the Go 1.8 release. We fix critical problems, including [critical security problems](https://go.dev/security), in supported releases as needed by issuing minor revisions (for example, Go 1.6.1, Go 1.6.2, and so on). +### Supported Jira versions + +#### Jira Server (On-Premise solution) + +We follow the [Atlassian Support End of Life Policy](https://confluence.atlassian.com/support/atlassian-support-end-of-life-policy-201851003.html): + +> Atlassian supports feature versions for two years after the first major iteration of that version was released (for example, we support Jira Core 7.2.x for 2 years after Jira 7.2.0 was released). + +#### Jira Cloud + +* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/) + +Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) is _currently_ not officially supported, because it is still in beta. + +### Official Jira API documentation + +* [Jira Server (On-Premise solution)](https://developer.atlassian.com/server/jira/platform/rest-apis/) +* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/) +* Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) + ### Sandbox environment for testing Jira offers sandbox test environments at http://go.atlassian.com/cloud-dev. From c6e60229db4d4f9bccc0aca9e36d1f21e38235fa Mon Sep 17 00:00:00 2001 From: Florian Kinder Date: Sun, 21 Aug 2022 18:07:23 +0200 Subject: [PATCH 004/189] refactor: Replaced github.com/pkg/errors with errors (#460) --- error.go | 11 ++++------- go.mod | 1 - go.sum | 2 -- jira.go | 5 ++--- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/error.go b/error.go index c049642..f4cf3de 100644 --- a/error.go +++ b/error.go @@ -6,8 +6,6 @@ import ( "fmt" "io" "strings" - - "github.com/pkg/errors" ) // Error message from Jira @@ -21,27 +19,26 @@ type Error struct { // NewJiraError creates a new jira Error func NewJiraError(resp *Response, httpError error) error { if resp == nil { - return errors.Wrap(httpError, "No response returned") + return fmt.Errorf("no response returned: %w", httpError) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { - return errors.Wrap(err, httpError.Error()) + return fmt.Errorf("%s: %w", httpError.Error(), err) } jerr := Error{HTTPError: httpError} contentType := resp.Header.Get("Content-Type") if strings.HasPrefix(contentType, "application/json") { err = json.Unmarshal(body, &jerr) if err != nil { - httpError = errors.Wrap(errors.New("could not parse JSON"), httpError.Error()) - return errors.Wrap(err, httpError.Error()) + return fmt.Errorf("%s: could not parse JSON: %w", httpError.Error(), err) } } else { if httpError == nil { return fmt.Errorf("got response status %s:%s", resp.Status, string(body)) } - return errors.Wrap(httpError, fmt.Sprintf("%s: %s", resp.Status, string(body))) + return fmt.Errorf("%s: %s: %w", resp.Status, string(body), httpError) } return &jerr diff --git a/go.mod b/go.mod index 0a13bf0..e5ebce3 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/golang-jwt/jwt/v4 v4.4.2 github.com/google/go-cmp v0.5.8 github.com/google/go-querystring v1.1.0 - github.com/pkg/errors v0.9.1 github.com/trivago/tgo v1.0.7 golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d ) diff --git a/go.sum b/go.sum index 10f2c32..7f282ab 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,6 @@ github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/jira.go b/jira.go index c86a308..28e0ccc 100644 --- a/jira.go +++ b/jira.go @@ -17,7 +17,6 @@ import ( jwt "github.com/golang-jwt/jwt/v4" "github.com/google/go-querystring/query" - "github.com/pkg/errors" ) // httpClient defines an interface for an http.Client implementation so that alternative @@ -483,7 +482,7 @@ func (t *CookieAuthTransport) RoundTrip(req *http.Request) (*http.Response, erro if t.SessionObject == nil { err := t.setSessionObject() if err != nil { - return nil, errors.Wrap(err, "cookieauth: no session object has been set") + return nil, fmt.Errorf("cookieauth: no session object has been set: %w", err) } } @@ -598,7 +597,7 @@ func (t *JWTAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) jwtStr, err := token.SignedString(t.Secret) if err != nil { - return nil, errors.Wrap(err, "jwtAuth: error signing JWT") + return nil, fmt.Errorf("jwtAuth: error signing JWT: %w", err) } req2.Header.Set("Authorization", fmt.Sprintf("JWT %s", jwtStr)) From de88769ae3e92ac13a1d264d7b497559bef1ab92 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 21 Aug 2022 18:18:13 +0200 Subject: [PATCH 005/189] Fixed unit test "TestError_NoResponse" (#493) Related #478 --- error_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/error_test.go b/error_test.go index fab8b0f..f9741e4 100644 --- a/error_test.go +++ b/error_test.go @@ -38,8 +38,8 @@ func TestError_NoResponse(t *testing.T) { t.Errorf("Expected the original error message: Got\n%s\n", msg) } - if !strings.Contains(msg, "No response") { - t.Errorf("Expected the 'No response' error message: Got\n%s\n", msg) + if !strings.Contains(msg, "no response returned") { + t.Errorf("Expected the 'no response returned' error message: Got\n%s\n", msg) } } From 79149bae348c90413f673ff1fc46d65effdb8f30 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Thu, 25 Aug 2022 15:18:13 +0300 Subject: [PATCH 006/189] GitHub Action: New workflow to label PRs when they have merge requests (#496) --- .github/workflows/label-merge-conflicts.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/label-merge-conflicts.yml diff --git a/.github/workflows/label-merge-conflicts.yml b/.github/workflows/label-merge-conflicts.yml new file mode 100644 index 0000000..c68f90f --- /dev/null +++ b/.github/workflows/label-merge-conflicts.yml @@ -0,0 +1,20 @@ +name: Auto-label merge conflicts + +on: + schedule: + - cron: "*/15 * * * *" + +# limit permissions +permissions: + contents: read + pull-requests: write + +jobs: + conflicts: + runs-on: ubuntu-latest + + steps: + - uses: mschilde/auto-label-merge-conflicts@v2.0 + with: + CONFLICT_LABEL_NAME: conflicts + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file From af74a7bdc66e1c80a60ad069ea9babb3fc976c74 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Thu, 25 Aug 2022 15:20:33 +0300 Subject: [PATCH 007/189] GitHub Actions: New action to label PRs (via actions/labeler) (#497) --- .github/labeler.yml | 3 +++ .github/workflows/label.yml | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/label.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..7d38c71 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,3 @@ +jira-onpremise: onpremise/**/* +jira-cloud: cloud/**/* +documentation: docs/**/* \ No newline at end of file diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml new file mode 100644 index 0000000..c2bd339 --- /dev/null +++ b/.github/workflows/label.yml @@ -0,0 +1,18 @@ +name: Label pull requests + +on: +- pull_request + +# limit permissions +permissions: + contents: read + pull-requests: write + +jobs: + build: + runs-on: ubuntu-latest + if: (github.actor != 'dependabot[bot]') + steps: + - uses: actions/labeler@v4 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file From 75d07fbdf78e9b6d549f11650aad477c997b6f4c Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Thu, 25 Aug 2022 15:22:44 +0300 Subject: [PATCH 008/189] Fix branch for unit tests --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index bda0521..6d7699f 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -3,7 +3,7 @@ name: Tests on: push: branches: - - master + - main pull_request: workflow_dispatch: schedule: From 6999a8aafe5e87f43812fccd141f2ec594f5b026 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Thu, 25 Aug 2022 15:22:53 +0300 Subject: [PATCH 009/189] GitHub Actions: Enable start of GitHub Action workflows by a button click --- .github/workflows/label-merge-conflicts.yml | 3 ++- .github/workflows/label.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/label-merge-conflicts.yml b/.github/workflows/label-merge-conflicts.yml index c68f90f..048cbff 100644 --- a/.github/workflows/label-merge-conflicts.yml +++ b/.github/workflows/label-merge-conflicts.yml @@ -1,8 +1,9 @@ name: Auto-label merge conflicts on: + workflow_dispatch: schedule: - - cron: "*/15 * * * *" + - cron: "*/15 * * * *" # limit permissions permissions: diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml index c2bd339..b37fcb9 100644 --- a/.github/workflows/label.yml +++ b/.github/workflows/label.yml @@ -1,7 +1,8 @@ name: Label pull requests on: -- pull_request + pull_request: + workflow_dispatch: # limit permissions permissions: From 7a8f03318dea0c29b1fe53c184d1f6ff8af8bf30 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 7 Sep 2022 20:54:11 +0200 Subject: [PATCH 010/189] :warning: Update warning about current main branch and v2 :warning: --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a35998c..4c4dd77 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,12 @@ ![Go client library for Atlassian Jira](./img/logo_small.png "Go client library for Atlassian Jira.") -## State of this library +## :warning: State of this library :warning: + +**v2 of this library is in development.** +**v2 will contain breaking changes :warning:** +**The current main branch can contains the development version of v2.** -v2 of this library is in development. The goals of v2 are: * idiomatic go usage @@ -21,7 +24,7 @@ The goals of v2 are: See our milestone [Road to v2](https://github.com/andygrunwald/go-jira/milestone/1) and provide feedback in [Development is kicking: Road to v2 🚀 #489](https://github.com/andygrunwald/go-jira/issues/489). Attention: The current `main` branch represents the v2 development version - we treat this version as unstable and breaking changes are expected. -If you want to stay more stable, please use v1.* - See our [releases](https://github.com/andygrunwald/go-jira/releases). +**If you want to stay more stable, please use v1.\*** - See our [releases](https://github.com/andygrunwald/go-jira/releases). Latest stable release: [v1.16.0](https://github.com/andygrunwald/go-jira/releases/tag/v1.16.0) ## Features From a746054fb80aa9b991e5afb10049feacdbc3f490 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 17:59:18 +0200 Subject: [PATCH 011/189] Split On-Premise and Cloud Clients #473 (#503) * Splitting Jira client into OnPremise and Cloud Both products look like the same, but the API differs. We split those into their own packages to make it explicit. Splitting was done by * copying the files * renaming the packages * fixing the path to mock-test-data * Fix test filenames for PermissionSchemeService * Remove legacy request with context function The function `http.NewRequestWithContext` has been added in Go 1.13. Before the release 1.13, to use Context we need create `http.Request` then use the method `WithContext` to create a new `http.Request` with Context from the existing `http.Request`. Doc: https://golang.org/doc/go1.13#net/http We don't support Go 1.13 anymore. That is why we can drop this function * Authentication transport: Split into own files * cloud/onpremise: Add basic READMEs * onpremise: fix import of onpremise package * CHANGELOG: Add chapter about how to migrate to v2 (with split clients) --- CHANGELOG.md | 37 + cloud/README.md | 5 + cloud/auth_transport.go | 17 + cloud/auth_transport_basic.go | 39 + cloud/auth_transport_basic_test.go | 51 + cloud/auth_transport_bearer.go | 41 + cloud/auth_transport_cookie.go | 106 + cloud/auth_transport_cookie_test.go | 119 + cloud/auth_transport_jwt.go | 87 + cloud/auth_transport_jwt_test.go | 31 + cloud/auth_transport_personal_access_token.go | 39 + ...th_transport_personal_access_token_test.go | 29 + authentication.go => cloud/authentication.go | 2 +- .../authentication_test.go | 2 +- board.go => cloud/board.go | 2 +- board_test.go => cloud/board_test.go | 12 +- component.go => cloud/component.go | 2 +- component_test.go => cloud/component_test.go | 2 +- customer.go => cloud/customer.go | 2 +- customer_test.go => cloud/customer_test.go | 2 +- error.go => cloud/error.go | 2 +- error_test.go => cloud/error_test.go | 2 +- {examples => cloud/examples}/addlabel/main.go | 2 +- .../examples}/basicauth/main.go | 5 +- {examples => cloud/examples}/create/main.go | 2 +- .../examples}/createwithcustomfields/main.go | 2 +- {examples => cloud/examples}/do/main.go | 2 +- .../examples}/ignorecerts/main.go | 2 +- {examples => cloud/examples}/jql/main.go | 2 +- .../examples}/newclient/main.go | 2 +- .../examples}/pagination/main.go | 2 +- .../examples}/renderedfields/main.go | 5 +- .../examples}/searchpages/main.go | 5 +- field.go => cloud/field.go | 2 +- field_test.go => cloud/field_test.go | 4 +- filter.go => cloud/filter.go | 2 +- filter_test.go => cloud/filter_test.go | 12 +- group.go => cloud/group.go | 2 +- group_test.go => cloud/group_test.go | 2 +- issue.go => cloud/issue.go | 2 +- issue_test.go => cloud/issue_test.go | 6 +- issuelinktype.go => cloud/issuelinktype.go | 2 +- .../issuelinktype_test.go | 4 +- jira.go => cloud/jira.go | 304 +-- jira_test.go => cloud/jira_test.go | 206 +- metaissue.go => cloud/metaissue.go | 2 +- metaissue_test.go => cloud/metaissue_test.go | 2 +- organization.go => cloud/organization.go | 2 +- .../organization_test.go | 2 +- .../permissionscheme.go | 2 +- .../permissionscheme_test.go | 10 +- priority.go => cloud/priority.go | 2 +- priority_test.go => cloud/priority_test.go | 4 +- project.go => cloud/project.go | 2 +- project_test.go => cloud/project_test.go | 8 +- request.go => cloud/request.go | 2 +- request_test.go => cloud/request_test.go | 2 +- resolution.go => cloud/resolution.go | 2 +- .../resolution_test.go | 4 +- role.go => cloud/role.go | 2 +- role_test.go => cloud/role_test.go | 10 +- servicedesk.go => cloud/servicedesk.go | 2 +- .../servicedesk_test.go | 2 +- sprint.go => cloud/sprint.go | 2 +- sprint_test.go => cloud/sprint_test.go | 4 +- status.go => cloud/status.go | 2 +- status_test.go => cloud/status_test.go | 4 +- statuscategory.go => cloud/statuscategory.go | 2 +- .../statuscategory_test.go | 4 +- types.go => cloud/types.go | 2 +- user.go => cloud/user.go | 2 +- user_test.go => cloud/user_test.go | 2 +- version.go => cloud/version.go | 2 +- version_test.go => cloud/version_test.go | 2 +- onpremise/README.md | 5 + onpremise/auth_transport.go | 17 + onpremise/auth_transport_basic.go | 39 + onpremise/auth_transport_basic_test.go | 51 + onpremise/auth_transport_bearer.go | 41 + onpremise/auth_transport_cookie.go | 106 + onpremise/auth_transport_cookie_test.go | 119 + onpremise/auth_transport_jwt.go | 87 + onpremise/auth_transport_jwt_test.go | 31 + .../auth_transport_personal_access_token.go | 39 + ...th_transport_personal_access_token_test.go | 29 + onpremise/authentication.go | 208 ++ onpremise/authentication_test.go | 321 +++ onpremise/board.go | 315 +++ onpremise/board_test.go | 266 +++ onpremise/component.go | 44 + onpremise/component_test.go | 29 + onpremise/customer.go | 72 + onpremise/customer_test.go | 56 + onpremise/error.go | 87 + onpremise/error_test.go | 205 ++ onpremise/examples/addlabel/main.go | 77 + onpremise/examples/basicauth/main.go | 48 + onpremise/examples/create/main.go | 63 + .../examples/createwithcustomfields/main.go | 74 + onpremise/examples/do/main.go | 23 + onpremise/examples/ignorecerts/main.go | 24 + onpremise/examples/jql/main.go | 42 + onpremise/examples/newclient/main.go | 16 + onpremise/examples/pagination/main.go | 55 + onpremise/examples/renderedfields/main.go | 67 + onpremise/examples/searchpages/main.go | 66 + onpremise/field.go | 55 + onpremise/field_test.go | 32 + onpremise/filter.go | 251 +++ onpremise/filter_test.go | 126 ++ onpremise/group.go | 179 ++ onpremise/group_test.go | 111 + onpremise/issue.go | 1598 ++++++++++++++ onpremise/issue_test.go | 1933 +++++++++++++++++ onpremise/issuelinktype.go | 141 ++ onpremise/issuelinktype_test.go | 118 + onpremise/jira.go | 345 +++ onpremise/jira_test.go | 452 ++++ onpremise/metaissue.go | 236 ++ onpremise/metaissue_test.go | 1078 +++++++++ onpremise/organization.go | 397 ++++ onpremise/organization_test.go | 325 +++ onpremise/permissionscheme.go | 82 + onpremise/permissionscheme_test.go | 106 + onpremise/priority.go | 44 + onpremise/priority_test.go | 32 + onpremise/project.go | 182 ++ onpremise/project_test.go | 162 ++ onpremise/request.go | 123 ++ onpremise/request_test.go | 199 ++ onpremise/resolution.go | 42 + onpremise/resolution_test.go | 32 + onpremise/role.go | 87 + onpremise/role_test.go | 107 + onpremise/servicedesk.go | 226 ++ onpremise/servicedesk_test.go | 430 ++++ onpremise/sprint.go | 125 ++ onpremise/sprint_test.go | 116 + onpremise/status.go | 47 + onpremise/status_test.go | 35 + onpremise/statuscategory.go | 51 + onpremise/statuscategory_test.go | 32 + onpremise/types.go | 9 + onpremise/user.go | 294 +++ onpremise/user_test.go | 192 ++ onpremise/version.go | 115 + onpremise/version_test.go | 112 + request_context.go | 24 - request_legacy.go | 29 - {mocks => testing/mock-data}/all_boards.json | 0 .../mock-data}/all_boards_filtered.json | 0 {mocks => testing/mock-data}/all_fields.json | 0 {mocks => testing/mock-data}/all_filters.json | 0 .../mock-data}/all_issuelinktypes.json | 0 .../mock-data}/all_permissionschemes.json | 0 .../mock-data}/all_priorities.json | 0 .../mock-data}/all_projects.json | 0 .../mock-data}/all_resolutions.json | 0 {mocks => testing/mock-data}/all_roles.json | 0 .../mock-data}/all_statuscategories.json | 0 .../mock-data}/all_statuses.json | 0 .../mock-data}/board_configuration.json | 0 .../mock-data}/favourite_filters.json | 0 {mocks => testing/mock-data}/filter.json | 0 .../mock-data}/issues_in_sprint.json | 0 {mocks => testing/mock-data}/my_filters.json | 0 .../mock-data}/no_permissionscheme.json | 0 .../mock-data}/no_permissionschemes.json | 0 {mocks => testing/mock-data}/no_role.json | 0 {mocks => testing/mock-data}/no_roles.json | 0 .../mock-data}/permissionscheme.json | 0 {mocks => testing/mock-data}/project.json | 0 .../mock-data}/remote_links.json | 0 {mocks => testing/mock-data}/role.json | 0 .../mock-data}/search_filters.json | 0 {mocks => testing/mock-data}/sprints.json | 0 .../mock-data}/sprints_filtered.json | 0 {mocks => testing/mock-data}/transitions.json | 0 178 files changed, 13783 insertions(+), 651 deletions(-) create mode 100644 cloud/README.md create mode 100644 cloud/auth_transport.go create mode 100644 cloud/auth_transport_basic.go create mode 100644 cloud/auth_transport_basic_test.go create mode 100644 cloud/auth_transport_bearer.go create mode 100644 cloud/auth_transport_cookie.go create mode 100644 cloud/auth_transport_cookie_test.go create mode 100644 cloud/auth_transport_jwt.go create mode 100644 cloud/auth_transport_jwt_test.go create mode 100644 cloud/auth_transport_personal_access_token.go create mode 100644 cloud/auth_transport_personal_access_token_test.go rename authentication.go => cloud/authentication.go (99%) rename authentication_test.go => cloud/authentication_test.go (99%) rename board.go => cloud/board.go (99%) rename board_test.go => cloud/board_test.go (94%) rename component.go => cloud/component.go (99%) rename component_test.go => cloud/component_test.go (99%) rename customer.go => cloud/customer.go (99%) rename customer_test.go => cloud/customer_test.go (99%) rename error.go => cloud/error.go (99%) rename error_test.go => cloud/error_test.go (99%) rename {examples => cloud/examples}/addlabel/main.go (96%) rename {examples => cloud/examples}/basicauth/main.go (94%) rename {examples => cloud/examples}/create/main.go (96%) rename {examples => cloud/examples}/createwithcustomfields/main.go (96%) rename {examples => cloud/examples}/do/main.go (89%) rename {examples => cloud/examples}/ignorecerts/main.go (91%) rename {examples => cloud/examples}/jql/main.go (95%) rename {examples => cloud/examples}/newclient/main.go (88%) rename {examples => cloud/examples}/pagination/main.go (96%) rename {examples => cloud/examples}/renderedfields/main.go (96%) rename {examples => cloud/examples}/searchpages/main.go (97%) rename field.go => cloud/field.go (99%) rename field_test.go => cloud/field_test.go (87%) rename filter.go => cloud/filter.go (99%) rename filter_test.go => cloud/filter_test.go (89%) rename group.go => cloud/group.go (99%) rename group_test.go => cloud/group_test.go (99%) rename issue.go => cloud/issue.go (99%) rename issue_test.go => cloud/issue_test.go (99%) rename issuelinktype.go => cloud/issuelinktype.go (99%) rename issuelinktype_test.go => cloud/issuelinktype_test.go (97%) rename jira.go => cloud/jira.go (51%) rename jira_test.go => cloud/jira_test.go (68%) rename metaissue.go => cloud/metaissue.go (99%) rename metaissue_test.go => cloud/metaissue_test.go (99%) rename organization.go => cloud/organization.go (99%) rename organization_test.go => cloud/organization_test.go (99%) rename permissionscheme.go => cloud/permissionscheme.go (99%) rename permissionschemes_test.go => cloud/permissionscheme_test.go (89%) rename priority.go => cloud/priority.go (99%) rename priority_test.go => cloud/priority_test.go (87%) rename project.go => cloud/project.go (99%) rename project_test.go => cloud/project_test.go (95%) rename request.go => cloud/request.go (99%) rename request_test.go => cloud/request_test.go (99%) rename resolution.go => cloud/resolution.go (98%) rename resolution_test.go => cloud/resolution_test.go (87%) rename role.go => cloud/role.go (99%) rename role_test.go => cloud/role_test.go (89%) rename servicedesk.go => cloud/servicedesk.go (99%) rename servicedesk_test.go => cloud/servicedesk_test.go (99%) rename sprint.go => cloud/sprint.go (99%) rename sprint_test.go => cloud/sprint_test.go (98%) rename status.go => cloud/status.go (99%) rename status_test.go => cloud/status_test.go (88%) rename statuscategory.go => cloud/statuscategory.go (99%) rename statuscategory_test.go => cloud/statuscategory_test.go (87%) rename types.go => cloud/types.go (92%) rename user.go => cloud/user.go (99%) rename user_test.go => cloud/user_test.go (99%) rename version.go => cloud/version.go (99%) rename version_test.go => cloud/version_test.go (99%) create mode 100644 onpremise/README.md create mode 100644 onpremise/auth_transport.go create mode 100644 onpremise/auth_transport_basic.go create mode 100644 onpremise/auth_transport_basic_test.go create mode 100644 onpremise/auth_transport_bearer.go create mode 100644 onpremise/auth_transport_cookie.go create mode 100644 onpremise/auth_transport_cookie_test.go create mode 100644 onpremise/auth_transport_jwt.go create mode 100644 onpremise/auth_transport_jwt_test.go create mode 100644 onpremise/auth_transport_personal_access_token.go create mode 100644 onpremise/auth_transport_personal_access_token_test.go create mode 100644 onpremise/authentication.go create mode 100644 onpremise/authentication_test.go create mode 100644 onpremise/board.go create mode 100644 onpremise/board_test.go create mode 100644 onpremise/component.go create mode 100644 onpremise/component_test.go create mode 100644 onpremise/customer.go create mode 100644 onpremise/customer_test.go create mode 100644 onpremise/error.go create mode 100644 onpremise/error_test.go create mode 100644 onpremise/examples/addlabel/main.go create mode 100644 onpremise/examples/basicauth/main.go create mode 100644 onpremise/examples/create/main.go create mode 100644 onpremise/examples/createwithcustomfields/main.go create mode 100644 onpremise/examples/do/main.go create mode 100644 onpremise/examples/ignorecerts/main.go create mode 100644 onpremise/examples/jql/main.go create mode 100644 onpremise/examples/newclient/main.go create mode 100644 onpremise/examples/pagination/main.go create mode 100644 onpremise/examples/renderedfields/main.go create mode 100644 onpremise/examples/searchpages/main.go create mode 100644 onpremise/field.go create mode 100644 onpremise/field_test.go create mode 100644 onpremise/filter.go create mode 100644 onpremise/filter_test.go create mode 100644 onpremise/group.go create mode 100644 onpremise/group_test.go create mode 100644 onpremise/issue.go create mode 100644 onpremise/issue_test.go create mode 100644 onpremise/issuelinktype.go create mode 100644 onpremise/issuelinktype_test.go create mode 100644 onpremise/jira.go create mode 100644 onpremise/jira_test.go create mode 100644 onpremise/metaissue.go create mode 100644 onpremise/metaissue_test.go create mode 100644 onpremise/organization.go create mode 100644 onpremise/organization_test.go create mode 100644 onpremise/permissionscheme.go create mode 100644 onpremise/permissionscheme_test.go create mode 100644 onpremise/priority.go create mode 100644 onpremise/priority_test.go create mode 100644 onpremise/project.go create mode 100644 onpremise/project_test.go create mode 100644 onpremise/request.go create mode 100644 onpremise/request_test.go create mode 100644 onpremise/resolution.go create mode 100644 onpremise/resolution_test.go create mode 100644 onpremise/role.go create mode 100644 onpremise/role_test.go create mode 100644 onpremise/servicedesk.go create mode 100644 onpremise/servicedesk_test.go create mode 100644 onpremise/sprint.go create mode 100644 onpremise/sprint_test.go create mode 100644 onpremise/status.go create mode 100644 onpremise/status_test.go create mode 100644 onpremise/statuscategory.go create mode 100644 onpremise/statuscategory_test.go create mode 100644 onpremise/types.go create mode 100644 onpremise/user.go create mode 100644 onpremise/user_test.go create mode 100644 onpremise/version.go create mode 100644 onpremise/version_test.go delete mode 100644 request_context.go delete mode 100644 request_legacy.go rename {mocks => testing/mock-data}/all_boards.json (100%) rename {mocks => testing/mock-data}/all_boards_filtered.json (100%) rename {mocks => testing/mock-data}/all_fields.json (100%) rename {mocks => testing/mock-data}/all_filters.json (100%) rename {mocks => testing/mock-data}/all_issuelinktypes.json (100%) rename {mocks => testing/mock-data}/all_permissionschemes.json (100%) rename {mocks => testing/mock-data}/all_priorities.json (100%) rename {mocks => testing/mock-data}/all_projects.json (100%) rename {mocks => testing/mock-data}/all_resolutions.json (100%) rename {mocks => testing/mock-data}/all_roles.json (100%) rename {mocks => testing/mock-data}/all_statuscategories.json (100%) rename {mocks => testing/mock-data}/all_statuses.json (100%) rename {mocks => testing/mock-data}/board_configuration.json (100%) rename {mocks => testing/mock-data}/favourite_filters.json (100%) rename {mocks => testing/mock-data}/filter.json (100%) rename {mocks => testing/mock-data}/issues_in_sprint.json (100%) rename {mocks => testing/mock-data}/my_filters.json (100%) rename {mocks => testing/mock-data}/no_permissionscheme.json (100%) rename {mocks => testing/mock-data}/no_permissionschemes.json (100%) rename {mocks => testing/mock-data}/no_role.json (100%) rename {mocks => testing/mock-data}/no_roles.json (100%) rename {mocks => testing/mock-data}/permissionscheme.json (100%) rename {mocks => testing/mock-data}/project.json (100%) rename {mocks => testing/mock-data}/remote_links.json (100%) rename {mocks => testing/mock-data}/role.json (100%) rename {mocks => testing/mock-data}/search_filters.json (100%) rename {mocks => testing/mock-data}/sprints.json (100%) rename {mocks => testing/mock-data}/sprints_filtered.json (100%) rename {mocks => testing/mock-data}/transitions.json (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5915ef4..35dd84e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,43 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [2.0]() (UNRELEASED) + +Version 2.0 is a bigger change with the main goal to make this library more reliable and future safe. +See https://github.com/andygrunwald/go-jira/issues/489 for details. + +### Migration + +#### Split of clients + +We moved from 1 client that handles On-Premise and Cloud to 2 clients that handle either On-Premise or Cloud. +Previously you used this library like: + +```go +import ( + "github.com/andygrunwald/go-jira" +) +``` + +In the new version, you need to decide if you interact with the Jira On-Premise or Jira Cloud version. +For the cloud version, you will import this library like + +```go +import ( + jira "github.com/andygrunwald/go-jira/cloud" +) +``` + +For On-Premise it looks like + +```go +import ( + jira "github.com/andygrunwald/go-jira/onpremise" +) +``` + +### Changes + ## [1.13.0](https://github.com/andygrunwald/go-jira/compare/v1.11.1...v1.13.0) (2020-10-25) diff --git a/cloud/README.md b/cloud/README.md new file mode 100644 index 0000000..0f395ea --- /dev/null +++ b/cloud/README.md @@ -0,0 +1,5 @@ +# Jira: Cloud client + +The API client library for cloud hosted Jira instances by Atlassian. + +For further information, please switch to the [README.md in the root folder](../README.md). \ No newline at end of file diff --git a/cloud/auth_transport.go b/cloud/auth_transport.go new file mode 100644 index 0000000..a2d76ca --- /dev/null +++ b/cloud/auth_transport.go @@ -0,0 +1,17 @@ +package cloud + +import "net/http" + +// cloneRequest returns a clone of the provided *http.Request. +// The clone is a shallow copy of the struct and its Header map. +func cloneRequest(r *http.Request) *http.Request { + // shallow copy of the struct + r2 := new(http.Request) + *r2 = *r + // deep copy of the Header + r2.Header = make(http.Header, len(r.Header)) + for k, s := range r.Header { + r2.Header[k] = append([]string(nil), s...) + } + return r2 +} diff --git a/cloud/auth_transport_basic.go b/cloud/auth_transport_basic.go new file mode 100644 index 0000000..b0e3c4c --- /dev/null +++ b/cloud/auth_transport_basic.go @@ -0,0 +1,39 @@ +package cloud + +import "net/http" + +// BasicAuthTransport is an http.RoundTripper that authenticates all requests +// using HTTP Basic Authentication with the provided username and password. +type BasicAuthTransport struct { + Username string + Password string + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +// RoundTrip implements the RoundTripper interface. We just add the +// basic auth and return the RoundTripper for this transport type. +func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req2 := cloneRequest(req) // per RoundTripper contract + + req2.SetBasicAuth(t.Username, t.Password) + return t.transport().RoundTrip(req2) +} + +// Client returns an *http.Client that makes requests that are authenticated +// using HTTP Basic Authentication. This is a nice little bit of sugar +// so we can just get the client instead of creating the client in the calling code. +// If it's necessary to send more information on client init, the calling code can +// always skip this and set the transport itself. +func (t *BasicAuthTransport) Client() *http.Client { + return &http.Client{Transport: t} +} + +func (t *BasicAuthTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} diff --git a/cloud/auth_transport_basic_test.go b/cloud/auth_transport_basic_test.go new file mode 100644 index 0000000..db4704e --- /dev/null +++ b/cloud/auth_transport_basic_test.go @@ -0,0 +1,51 @@ +package cloud + +import ( + "net/http" + "testing" +) + +func TestBasicAuthTransport(t *testing.T) { + setup() + defer teardown() + + username, password := "username", "password" + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + u, p, ok := r.BasicAuth() + if !ok { + t.Errorf("request does not contain basic auth credentials") + } + if u != username { + t.Errorf("request contained basic auth username %q, want %q", u, username) + } + if p != password { + t.Errorf("request contained basic auth password %q, want %q", p, password) + } + }) + + tp := &BasicAuthTransport{ + Username: username, + Password: password, + } + + basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) + req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient.Do(req, nil) +} + +func TestBasicAuthTransport_transport(t *testing.T) { + // default transport + tp := &BasicAuthTransport{} + if tp.transport() != http.DefaultTransport { + t.Errorf("Expected http.DefaultTransport to be used.") + } + + // custom transport + tp = &BasicAuthTransport{ + Transport: &http.Transport{}, + } + if tp.transport() == http.DefaultTransport { + t.Errorf("Expected custom transport to be used.") + } +} diff --git a/cloud/auth_transport_bearer.go b/cloud/auth_transport_bearer.go new file mode 100644 index 0000000..9ff3f90 --- /dev/null +++ b/cloud/auth_transport_bearer.go @@ -0,0 +1,41 @@ +package cloud + +import ( + "fmt" + "net/http" +) + +// BearerAuthTransport is a http.RoundTripper that authenticates all requests +// using Jira's bearer (oauth 2.0 (3lo)) based authentication. +type BearerAuthTransport struct { + Token string + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +// RoundTrip implements the RoundTripper interface. We just add the +// bearer token and return the RoundTripper for this transport type. +func (t *BearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req2 := cloneRequest(req) // per RoundTripper contract + + req2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.Token)) + return t.transport().RoundTrip(req2) +} + +// Client returns an *http.Client that makes requests that are authenticated +// using HTTP Basic Authentication. This is a nice little bit of sugar +// so we can just get the client instead of creating the client in the calling code. +// If it's necessary to send more information on client init, the calling code can +// always skip this and set the transport itself. +func (t *BearerAuthTransport) Client() *http.Client { + return &http.Client{Transport: t} +} + +func (t *BearerAuthTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} diff --git a/cloud/auth_transport_cookie.go b/cloud/auth_transport_cookie.go new file mode 100644 index 0000000..bd6e4b4 --- /dev/null +++ b/cloud/auth_transport_cookie.go @@ -0,0 +1,106 @@ +package cloud + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "time" +) + +// CookieAuthTransport is an http.RoundTripper that authenticates all requests +// using Jira's cookie-based authentication. +// +// Note that it is generally preferable to use HTTP BASIC authentication with the REST API. +// However, this resource may be used to mimic the behaviour of Jira's log-in page (e.g. to display log-in errors to a user). +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session +type CookieAuthTransport struct { + Username string + Password string + AuthURL string + + // SessionObject is the authenticated cookie string.s + // It's passed in each call to prove the client is authenticated. + SessionObject []*http.Cookie + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +// RoundTrip adds the session object to the request. +func (t *CookieAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + if t.SessionObject == nil { + err := t.setSessionObject() + if err != nil { + return nil, fmt.Errorf("cookieauth: no session object has been set: %w", err) + } + } + + req2 := cloneRequest(req) // per RoundTripper contract + for _, cookie := range t.SessionObject { + // Don't add an empty value cookie to the request + if cookie.Value != "" { + req2.AddCookie(cookie) + } + } + + return t.transport().RoundTrip(req2) +} + +// Client returns an *http.Client that makes requests that are authenticated +// using cookie authentication +func (t *CookieAuthTransport) Client() *http.Client { + return &http.Client{Transport: t} +} + +// setSessionObject attempts to authenticate the user and set +// the session object (e.g. cookie) +func (t *CookieAuthTransport) setSessionObject() error { + req, err := t.buildAuthRequest() + if err != nil { + return err + } + + var authClient = &http.Client{ + Timeout: time.Second * 60, + } + resp, err := authClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + t.SessionObject = resp.Cookies() + return nil +} + +// getAuthRequest assembles the request to get the authenticated cookie +func (t *CookieAuthTransport) buildAuthRequest() (*http.Request, error) { + body := struct { + Username string `json:"username"` + Password string `json:"password"` + }{ + t.Username, + t.Password, + } + + b := new(bytes.Buffer) + json.NewEncoder(b).Encode(body) + + req, err := http.NewRequest("POST", t.AuthURL, b) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + return req, nil +} + +func (t *CookieAuthTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} diff --git a/cloud/auth_transport_cookie_test.go b/cloud/auth_transport_cookie_test.go new file mode 100644 index 0000000..c9d1cfc --- /dev/null +++ b/cloud/auth_transport_cookie_test.go @@ -0,0 +1,119 @@ +package cloud + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +// Test that the cookie in the transport is the cookie returned in the header +func TestCookieAuthTransport_SessionObject_Exists(t *testing.T) { + setup() + defer teardown() + + testCookie := &http.Cookie{Name: "test", Value: "test"} + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + cookies := r.Cookies() + + if len(cookies) < 1 { + t.Errorf("No cookies set") + } + + if cookies[0].Name != testCookie.Name { + t.Errorf("Cookie names don't match, expected %v, got %v", testCookie.Name, cookies[0].Name) + } + + if cookies[0].Value != testCookie.Value { + t.Errorf("Cookie values don't match, expected %v, got %v", testCookie.Value, cookies[0].Value) + } + }) + + tp := &CookieAuthTransport{ + Username: "username", + Password: "password", + AuthURL: "https://some.jira.com/rest/auth/1/session", + SessionObject: []*http.Cookie{testCookie}, + } + + basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) + req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient.Do(req, nil) +} + +// Test that an empty cookie in the transport is not returned in the header +func TestCookieAuthTransport_SessionObject_ExistsWithEmptyCookie(t *testing.T) { + setup() + defer teardown() + + emptyCookie := &http.Cookie{Name: "empty_cookie", Value: ""} + testCookie := &http.Cookie{Name: "test", Value: "test"} + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + cookies := r.Cookies() + + if len(cookies) > 1 { + t.Errorf("The empty cookie should not have been added") + } + + if cookies[0].Name != testCookie.Name { + t.Errorf("Cookie names don't match, expected %v, got %v", testCookie.Name, cookies[0].Name) + } + + if cookies[0].Value != testCookie.Value { + t.Errorf("Cookie values don't match, expected %v, got %v", testCookie.Value, cookies[0].Value) + } + }) + + tp := &CookieAuthTransport{ + Username: "username", + Password: "password", + AuthURL: "https://some.jira.com/rest/auth/1/session", + SessionObject: []*http.Cookie{emptyCookie, testCookie}, + } + + basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) + req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient.Do(req, nil) +} + +// Test that if no cookie is in the transport, it checks for a cookie +func TestCookieAuthTransport_SessionObject_DoesNotExist(t *testing.T) { + setup() + defer teardown() + + testCookie := &http.Cookie{Name: "does_not_exist", Value: "does_not_exist"} + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + http.SetCookie(w, testCookie) + w.Write([]byte(`OK`)) + })) + defer ts.Close() + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + cookies := r.Cookies() + + if len(cookies) < 1 { + t.Errorf("No cookies set") + } + + if cookies[0].Name != testCookie.Name { + t.Errorf("Cookie names don't match, expected %v, got %v", testCookie.Name, cookies[0].Name) + } + + if cookies[0].Value != testCookie.Value { + t.Errorf("Cookie values don't match, expected %v, got %v", testCookie.Value, cookies[0].Value) + } + }) + + tp := &CookieAuthTransport{ + Username: "username", + Password: "password", + AuthURL: ts.URL, + } + + basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) + req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient.Do(req, nil) +} diff --git a/cloud/auth_transport_jwt.go b/cloud/auth_transport_jwt.go new file mode 100644 index 0000000..dc0ac49 --- /dev/null +++ b/cloud/auth_transport_jwt.go @@ -0,0 +1,87 @@ +package cloud + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "net/http" + "net/url" + "sort" + "strings" + "time" + + jwt "github.com/golang-jwt/jwt/v4" +) + +// JWTAuthTransport is an http.RoundTripper that authenticates all requests +// using Jira's JWT based authentication. +// +// NOTE: this form of auth should be used by add-ons installed from the Atlassian marketplace. +// +// Jira docs: https://developer.atlassian.com/cloud/jira/platform/understanding-jwt +// Examples in other languages: +// +// https://bitbucket.org/atlassian/atlassian-jwt-ruby/src/d44a8e7a4649e4f23edaa784402655fda7c816ea/lib/atlassian/jwt.rb +// https://bitbucket.org/atlassian/atlassian-jwt-py/src/master/atlassian_jwt/url_utils.py +type JWTAuthTransport struct { + Secret []byte + Issuer string + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +func (t *JWTAuthTransport) Client() *http.Client { + return &http.Client{Transport: t} +} + +func (t *JWTAuthTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} + +// RoundTrip adds the session object to the request. +func (t *JWTAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req2 := cloneRequest(req) // per RoundTripper contract + exp := time.Duration(59) * time.Second + qsh := t.createQueryStringHash(req.Method, req2.URL) + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "iss": t.Issuer, + "iat": time.Now().Unix(), + "exp": time.Now().Add(exp).Unix(), + "qsh": qsh, + }) + + jwtStr, err := token.SignedString(t.Secret) + if err != nil { + return nil, fmt.Errorf("jwtAuth: error signing JWT: %w", err) + } + + req2.Header.Set("Authorization", fmt.Sprintf("JWT %s", jwtStr)) + return t.transport().RoundTrip(req2) +} + +func (t *JWTAuthTransport) createQueryStringHash(httpMethod string, jiraURL *url.URL) string { + canonicalRequest := t.canonicalizeRequest(httpMethod, jiraURL) + h := sha256.Sum256([]byte(canonicalRequest)) + return hex.EncodeToString(h[:]) +} + +func (t *JWTAuthTransport) canonicalizeRequest(httpMethod string, jiraURL *url.URL) string { + path := "/" + strings.Replace(strings.Trim(jiraURL.Path, "/"), "&", "%26", -1) + + var canonicalQueryString []string + for k, v := range jiraURL.Query() { + if k == "jwt" { + continue + } + param := url.QueryEscape(k) + value := url.QueryEscape(strings.Join(v, "")) + canonicalQueryString = append(canonicalQueryString, strings.Replace(strings.Join([]string{param, value}, "="), "+", "%20", -1)) + } + sort.Strings(canonicalQueryString) + return fmt.Sprintf("%s&%s&%s", strings.ToUpper(httpMethod), path, strings.Join(canonicalQueryString, "&")) +} diff --git a/cloud/auth_transport_jwt_test.go b/cloud/auth_transport_jwt_test.go new file mode 100644 index 0000000..e577bb6 --- /dev/null +++ b/cloud/auth_transport_jwt_test.go @@ -0,0 +1,31 @@ +package cloud + +import ( + "net/http" + "strings" + "testing" +) + +func TestJWTAuthTransport_HeaderContainsJWT(t *testing.T) { + setup() + defer teardown() + + sharedSecret := []byte("ssshh,it's a secret") + issuer := "add-on.key" + + jwtTransport := &JWTAuthTransport{ + Secret: sharedSecret, + Issuer: issuer, + } + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + // look for the presence of the JWT in the header + val := r.Header.Get("Authorization") + if !strings.Contains(val, "JWT ") { + t.Errorf("request does not contain JWT in the Auth header") + } + }) + + jwtClient, _ := NewClient(jwtTransport.Client(), testServer.URL) + jwtClient.Issue.Get("TEST-1", nil) +} diff --git a/cloud/auth_transport_personal_access_token.go b/cloud/auth_transport_personal_access_token.go new file mode 100644 index 0000000..fb2f339 --- /dev/null +++ b/cloud/auth_transport_personal_access_token.go @@ -0,0 +1,39 @@ +package cloud + +import "net/http" + +// PATAuthTransport is an http.RoundTripper that authenticates all requests +// using the Personal Access Token specified. +// See here for more info: https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html +type PATAuthTransport struct { + // Token is the key that was provided by Jira when creating the Personal Access Token. + Token string + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +// RoundTrip implements the RoundTripper interface. We just add the +// basic auth and return the RoundTripper for this transport type. +func (t *PATAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req2 := cloneRequest(req) // per RoundTripper contract + req2.Header.Set("Authorization", "Bearer "+t.Token) + return t.transport().RoundTrip(req2) +} + +// Client returns an *http.Client that makes requests that are authenticated +// using HTTP Basic Authentication. This is a nice little bit of sugar +// so we can just get the client instead of creating the client in the calling code. +// If it's necessary to send more information on client init, the calling code can +// always skip this and set the transport itself. +func (t *PATAuthTransport) Client() *http.Client { + return &http.Client{Transport: t} +} + +func (t *PATAuthTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} diff --git a/cloud/auth_transport_personal_access_token_test.go b/cloud/auth_transport_personal_access_token_test.go new file mode 100644 index 0000000..5ad4926 --- /dev/null +++ b/cloud/auth_transport_personal_access_token_test.go @@ -0,0 +1,29 @@ +package cloud + +import ( + "net/http" + "testing" +) + +func TestPATAuthTransport_HeaderContainsAuth(t *testing.T) { + setup() + defer teardown() + + token := "shhh, it's a token" + + patTransport := &PATAuthTransport{ + Token: token, + } + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + val := r.Header.Get("Authorization") + expected := "Bearer " + token + if val != expected { + t.Errorf("request does not contain bearer token in the Authorization header.") + } + }) + + client, _ := NewClient(patTransport.Client(), testServer.URL) + client.User.GetSelf() + +} diff --git a/authentication.go b/cloud/authentication.go similarity index 99% rename from authentication.go rename to cloud/authentication.go index 2f41f8c..fc0828c 100644 --- a/authentication.go +++ b/cloud/authentication.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/authentication_test.go b/cloud/authentication_test.go similarity index 99% rename from authentication_test.go rename to cloud/authentication_test.go index 0ceea89..e94b5ed 100644 --- a/authentication_test.go +++ b/cloud/authentication_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "bytes" diff --git a/board.go b/cloud/board.go similarity index 99% rename from board.go rename to cloud/board.go index 890d6e0..25b4783 100644 --- a/board.go +++ b/cloud/board.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/board_test.go b/cloud/board_test.go similarity index 94% rename from board_test.go rename to cloud/board_test.go index 0806b76..699776c 100644 --- a/board_test.go +++ b/cloud/board_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" @@ -12,7 +12,7 @@ func TestBoardService_GetAllBoards(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/agile/1.0/board" - raw, err := os.ReadFile("./mocks/all_boards.json") + raw, err := os.ReadFile("../testing/mock-data/all_boards.json") if err != nil { t.Error(err.Error()) } @@ -37,7 +37,7 @@ func TestBoardService_GetAllBoards_WithFilter(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/agile/1.0/board" - raw, err := os.ReadFile("./mocks/all_boards_filtered.json") + raw, err := os.ReadFile("../testing/mock-data/all_boards_filtered.json") if err != nil { t.Error(err.Error()) } @@ -160,7 +160,7 @@ func TestBoardService_GetAllSprints(t *testing.T) { testAPIEndpoint := "/rest/agile/1.0/board/123/sprint" - raw, err := os.ReadFile("./mocks/sprints.json") + raw, err := os.ReadFile("../testing/mock-data/sprints.json") if err != nil { t.Error(err.Error()) } @@ -192,7 +192,7 @@ func TestBoardService_GetAllSprintsWithOptions(t *testing.T) { testAPIEndpoint := "/rest/agile/1.0/board/123/sprint" - raw, err := os.ReadFile("./mocks/sprints_filtered.json") + raw, err := os.ReadFile("../testing/mock-data/sprints_filtered.json") if err != nil { t.Error(err.Error()) } @@ -223,7 +223,7 @@ func TestBoardService_GetBoardConfigoration(t *testing.T) { defer teardown() testAPIEndpoint := "/rest/agile/1.0/board/35/configuration" - raw, err := os.ReadFile("./mocks/board_configuration.json") + raw, err := os.ReadFile("../testing/mock-data/board_configuration.json") if err != nil { t.Error(err.Error()) } diff --git a/component.go b/cloud/component.go similarity index 99% rename from component.go rename to cloud/component.go index b76fe0c..51a36b6 100644 --- a/component.go +++ b/cloud/component.go @@ -1,4 +1,4 @@ -package jira +package cloud import "context" diff --git a/component_test.go b/cloud/component_test.go similarity index 99% rename from component_test.go rename to cloud/component_test.go index 527cdbe..e071945 100644 --- a/component_test.go +++ b/cloud/component_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" diff --git a/customer.go b/cloud/customer.go similarity index 99% rename from customer.go rename to cloud/customer.go index 3df31c1..fb43ff9 100644 --- a/customer.go +++ b/cloud/customer.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/customer_test.go b/cloud/customer_test.go similarity index 99% rename from customer_test.go rename to cloud/customer_test.go index d8c3714..d12db6b 100644 --- a/customer_test.go +++ b/cloud/customer_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" diff --git a/error.go b/cloud/error.go similarity index 99% rename from error.go rename to cloud/error.go index f4cf3de..7a8ca2c 100644 --- a/error.go +++ b/cloud/error.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "bytes" diff --git a/error_test.go b/cloud/error_test.go similarity index 99% rename from error_test.go rename to cloud/error_test.go index f9741e4..cb60c1c 100644 --- a/error_test.go +++ b/cloud/error_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "errors" diff --git a/examples/addlabel/main.go b/cloud/examples/addlabel/main.go similarity index 96% rename from examples/addlabel/main.go rename to cloud/examples/addlabel/main.go index 3b0e82d..d7dc3fa 100644 --- a/examples/addlabel/main.go +++ b/cloud/examples/addlabel/main.go @@ -8,7 +8,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira" + jira "github.com/andygrunwald/go-jira/cloud" "golang.org/x/term" ) diff --git a/examples/basicauth/main.go b/cloud/examples/basicauth/main.go similarity index 94% rename from examples/basicauth/main.go rename to cloud/examples/basicauth/main.go index 04a0dae..c9d6eb0 100644 --- a/examples/basicauth/main.go +++ b/cloud/examples/basicauth/main.go @@ -3,12 +3,13 @@ package main import ( "bufio" "fmt" - "golang.org/x/term" "os" "strings" "syscall" - jira "github.com/andygrunwald/go-jira" + "golang.org/x/term" + + jira "github.com/andygrunwald/go-jira/cloud" ) func main() { diff --git a/examples/create/main.go b/cloud/examples/create/main.go similarity index 96% rename from examples/create/main.go rename to cloud/examples/create/main.go index 00a00c1..b803de0 100644 --- a/examples/create/main.go +++ b/cloud/examples/create/main.go @@ -7,7 +7,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira" + jira "github.com/andygrunwald/go-jira/cloud" "golang.org/x/term" ) diff --git a/examples/createwithcustomfields/main.go b/cloud/examples/createwithcustomfields/main.go similarity index 96% rename from examples/createwithcustomfields/main.go rename to cloud/examples/createwithcustomfields/main.go index 2a6922a..8ebe424 100644 --- a/examples/createwithcustomfields/main.go +++ b/cloud/examples/createwithcustomfields/main.go @@ -7,7 +7,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira" + jira "github.com/andygrunwald/go-jira/cloud" "github.com/trivago/tgo/tcontainer" "golang.org/x/term" ) diff --git a/examples/do/main.go b/cloud/examples/do/main.go similarity index 89% rename from examples/do/main.go rename to cloud/examples/do/main.go index 750e77d..b13adc2 100644 --- a/examples/do/main.go +++ b/cloud/examples/do/main.go @@ -3,7 +3,7 @@ package main import ( "fmt" - jira "github.com/andygrunwald/go-jira" + jira "github.com/andygrunwald/go-jira/cloud" ) func main() { diff --git a/examples/ignorecerts/main.go b/cloud/examples/ignorecerts/main.go similarity index 91% rename from examples/ignorecerts/main.go rename to cloud/examples/ignorecerts/main.go index 6fcc602..e5325bc 100644 --- a/examples/ignorecerts/main.go +++ b/cloud/examples/ignorecerts/main.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - jira "github.com/andygrunwald/go-jira" + jira "github.com/andygrunwald/go-jira/cloud" ) func main() { diff --git a/examples/jql/main.go b/cloud/examples/jql/main.go similarity index 95% rename from examples/jql/main.go rename to cloud/examples/jql/main.go index 9cb05f5..de980db 100644 --- a/examples/jql/main.go +++ b/cloud/examples/jql/main.go @@ -3,7 +3,7 @@ package main import ( "fmt" - jira "github.com/andygrunwald/go-jira" + jira "github.com/andygrunwald/go-jira/cloud" ) func main() { diff --git a/examples/newclient/main.go b/cloud/examples/newclient/main.go similarity index 88% rename from examples/newclient/main.go rename to cloud/examples/newclient/main.go index e0bec52..9b259d8 100644 --- a/examples/newclient/main.go +++ b/cloud/examples/newclient/main.go @@ -3,7 +3,7 @@ package main import ( "fmt" - jira "github.com/andygrunwald/go-jira" + jira "github.com/andygrunwald/go-jira/cloud" ) func main() { diff --git a/examples/pagination/main.go b/cloud/examples/pagination/main.go similarity index 96% rename from examples/pagination/main.go rename to cloud/examples/pagination/main.go index 571e9e1..69a9857 100644 --- a/examples/pagination/main.go +++ b/cloud/examples/pagination/main.go @@ -3,7 +3,7 @@ package main import ( "fmt" - jira "github.com/andygrunwald/go-jira" + jira "github.com/andygrunwald/go-jira/cloud" ) // GetAllIssues will implement pagination of api and get all the issues. diff --git a/examples/renderedfields/main.go b/cloud/examples/renderedfields/main.go similarity index 96% rename from examples/renderedfields/main.go rename to cloud/examples/renderedfields/main.go index b72c946..92e8aa9 100644 --- a/examples/renderedfields/main.go +++ b/cloud/examples/renderedfields/main.go @@ -3,13 +3,14 @@ package main import ( "bufio" "fmt" - "golang.org/x/term" "net/http" "os" "strings" "syscall" - jira "github.com/andygrunwald/go-jira" + "golang.org/x/term" + + jira "github.com/andygrunwald/go-jira/cloud" ) func main() { diff --git a/examples/searchpages/main.go b/cloud/examples/searchpages/main.go similarity index 97% rename from examples/searchpages/main.go rename to cloud/examples/searchpages/main.go index 754a5a0..f1dac69 100644 --- a/examples/searchpages/main.go +++ b/cloud/examples/searchpages/main.go @@ -3,13 +3,14 @@ package main import ( "bufio" "fmt" - jira "github.com/andygrunwald/go-jira" - "golang.org/x/term" "log" "os" "strings" "syscall" "time" + + jira "github.com/andygrunwald/go-jira/cloud" + "golang.org/x/term" ) func main() { diff --git a/field.go b/cloud/field.go similarity index 99% rename from field.go rename to cloud/field.go index b14057d..4cc369a 100644 --- a/field.go +++ b/cloud/field.go @@ -1,4 +1,4 @@ -package jira +package cloud import "context" diff --git a/field_test.go b/cloud/field_test.go similarity index 87% rename from field_test.go rename to cloud/field_test.go index 9bdf361..55ef9f5 100644 --- a/field_test.go +++ b/cloud/field_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" @@ -12,7 +12,7 @@ func TestFieldService_GetList(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/field" - raw, err := os.ReadFile("./mocks/all_fields.json") + raw, err := os.ReadFile("../testing/mock-data/all_fields.json") if err != nil { t.Error(err.Error()) } diff --git a/filter.go b/cloud/filter.go similarity index 99% rename from filter.go rename to cloud/filter.go index f40f3a5..6a0937c 100644 --- a/filter.go +++ b/cloud/filter.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/filter_test.go b/cloud/filter_test.go similarity index 89% rename from filter_test.go rename to cloud/filter_test.go index 5d19fc3..add1acc 100644 --- a/filter_test.go +++ b/cloud/filter_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" @@ -11,7 +11,7 @@ func TestFilterService_GetList(t *testing.T) { setup() defer teardown() testAPIEndpoint := "/rest/api/2/filter" - raw, err := os.ReadFile("./mocks/all_filters.json") + raw, err := os.ReadFile("../testing/mock-data/all_filters.json") if err != nil { t.Error(err.Error()) } @@ -34,7 +34,7 @@ func TestFilterService_Get(t *testing.T) { setup() defer teardown() testAPIEndpoint := "/rest/api/2/filter/10000" - raw, err := os.ReadFile("./mocks/filter.json") + raw, err := os.ReadFile("../testing/mock-data/filter.json") if err != nil { t.Error(err.Error()) } @@ -58,7 +58,7 @@ func TestFilterService_GetFavouriteList(t *testing.T) { setup() defer teardown() testAPIEndpoint := "/rest/api/2/filter/favourite" - raw, err := os.ReadFile("./mocks/favourite_filters.json") + raw, err := os.ReadFile("../testing/mock-data/favourite_filters.json") if err != nil { t.Error(err.Error()) } @@ -81,7 +81,7 @@ func TestFilterService_GetMyFilters(t *testing.T) { setup() defer teardown() testAPIEndpoint := "/rest/api/3/filter/my" - raw, err := os.ReadFile("./mocks/my_filters.json") + raw, err := os.ReadFile("../testing/mock-data/my_filters.json") if err != nil { t.Error(err.Error()) } @@ -105,7 +105,7 @@ func TestFilterService_Search(t *testing.T) { setup() defer teardown() testAPIEndpoint := "/rest/api/3/filter/search" - raw, err := os.ReadFile("./mocks/search_filters.json") + raw, err := os.ReadFile("../testing/mock-data/search_filters.json") if err != nil { t.Error(err.Error()) } diff --git a/group.go b/cloud/group.go similarity index 99% rename from group.go rename to cloud/group.go index f78c681..39fb295 100644 --- a/group.go +++ b/cloud/group.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/group_test.go b/cloud/group_test.go similarity index 99% rename from group_test.go rename to cloud/group_test.go index e4503dd..98b48f2 100644 --- a/group_test.go +++ b/cloud/group_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" diff --git a/issue.go b/cloud/issue.go similarity index 99% rename from issue.go rename to cloud/issue.go index 6cb6781..dc17183 100644 --- a/issue.go +++ b/cloud/issue.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "bytes" diff --git a/issue_test.go b/cloud/issue_test.go similarity index 99% rename from issue_test.go rename to cloud/issue_test.go index a867535..01c8d7f 100644 --- a/issue_test.go +++ b/cloud/issue_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "encoding/json" @@ -823,7 +823,7 @@ func TestIssueService_GetTransitions(t *testing.T) { testAPIEndpoint := "/rest/api/2/issue/123/transitions" - raw, err := os.ReadFile("./mocks/transitions.json") + raw, err := os.ReadFile("../testing/mock-data/transitions.json") if err != nil { t.Error(err.Error()) } @@ -1787,7 +1787,7 @@ func TestIssueService_GetRemoteLinks(t *testing.T) { testAPIEndpoint := "/rest/api/2/issue/123/remotelink" - raw, err := os.ReadFile("./mocks/remote_links.json") + raw, err := os.ReadFile("../testing/mock-data/remote_links.json") if err != nil { t.Error(err.Error()) } diff --git a/issuelinktype.go b/cloud/issuelinktype.go similarity index 99% rename from issuelinktype.go rename to cloud/issuelinktype.go index a9f7af2..a65e88e 100644 --- a/issuelinktype.go +++ b/cloud/issuelinktype.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/issuelinktype_test.go b/cloud/issuelinktype_test.go similarity index 97% rename from issuelinktype_test.go rename to cloud/issuelinktype_test.go index 8e01d83..f33a81b 100644 --- a/issuelinktype_test.go +++ b/cloud/issuelinktype_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" @@ -12,7 +12,7 @@ func TestIssueLinkTypeService_GetList(t *testing.T) { defer teardown() testAPIEndpoint := "/rest/api/2/issueLinkType" - raw, err := os.ReadFile("./mocks/all_issuelinktypes.json") + raw, err := os.ReadFile("../testing/mock-data/all_issuelinktypes.json") if err != nil { t.Error(err.Error()) } diff --git a/jira.go b/cloud/jira.go similarity index 51% rename from jira.go rename to cloud/jira.go index 28e0ccc..bd1894b 100644 --- a/jira.go +++ b/cloud/jira.go @@ -1,21 +1,16 @@ -package jira +package cloud import ( "bytes" "context" - "crypto/sha256" - "encoding/hex" "encoding/json" "fmt" "io" "net/http" "net/url" "reflect" - "sort" "strings" - "time" - jwt "github.com/golang-jwt/jwt/v4" "github.com/google/go-querystring/query" ) @@ -126,7 +121,7 @@ func (c *Client) NewRawRequestWithContext(ctx context.Context, method, urlStr st u := c.baseURL.ResolveReference(rel) - req, err := newRequestWithContext(ctx, method, u.String(), body) + req, err := http.NewRequestWithContext(ctx, method, u.String(), body) if err != nil { return nil, err } @@ -178,7 +173,7 @@ func (c *Client) NewRequestWithContext(ctx context.Context, method, urlStr strin } } - req, err := newRequestWithContext(ctx, method, u.String(), buf) + req, err := http.NewRequestWithContext(ctx, method, u.String(), buf) if err != nil { return nil, err } @@ -243,7 +238,7 @@ func (c *Client) NewMultiPartRequestWithContext(ctx context.Context, method, url u := c.baseURL.ResolveReference(rel) - req, err := newRequestWithContext(ctx, method, u.String(), buf) + req, err := http.NewRequestWithContext(ctx, method, u.String(), buf) if err != nil { return nil, err } @@ -348,294 +343,3 @@ func (r *Response) populatePageValues(v interface{}) { r.Total = value.Total } } - -// BasicAuthTransport is an http.RoundTripper that authenticates all requests -// using HTTP Basic Authentication with the provided username and password. -type BasicAuthTransport struct { - Username string - Password string - - // Transport is the underlying HTTP transport to use when making requests. - // It will default to http.DefaultTransport if nil. - Transport http.RoundTripper -} - -// RoundTrip implements the RoundTripper interface. We just add the -// basic auth and return the RoundTripper for this transport type. -func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req2 := cloneRequest(req) // per RoundTripper contract - - req2.SetBasicAuth(t.Username, t.Password) - return t.transport().RoundTrip(req2) -} - -// Client returns an *http.Client that makes requests that are authenticated -// using HTTP Basic Authentication. This is a nice little bit of sugar -// so we can just get the client instead of creating the client in the calling code. -// If it's necessary to send more information on client init, the calling code can -// always skip this and set the transport itself. -func (t *BasicAuthTransport) Client() *http.Client { - return &http.Client{Transport: t} -} - -func (t *BasicAuthTransport) transport() http.RoundTripper { - if t.Transport != nil { - return t.Transport - } - return http.DefaultTransport -} - -// BearerAuthTransport is a http.RoundTripper that authenticates all requests -// using Jira's bearer (oauth 2.0 (3lo)) based authentication. -type BearerAuthTransport struct { - Token string - - // Transport is the underlying HTTP transport to use when making requests. - // It will default to http.DefaultTransport if nil. - Transport http.RoundTripper -} - -// RoundTrip implements the RoundTripper interface. We just add the -// bearer token and return the RoundTripper for this transport type. -func (t *BearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req2 := cloneRequest(req) // per RoundTripper contract - - req2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.Token)) - return t.transport().RoundTrip(req2) -} - -// Client returns an *http.Client that makes requests that are authenticated -// using HTTP Basic Authentication. This is a nice little bit of sugar -// so we can just get the client instead of creating the client in the calling code. -// If it's necessary to send more information on client init, the calling code can -// always skip this and set the transport itself. -func (t *BearerAuthTransport) Client() *http.Client { - return &http.Client{Transport: t} -} - -func (t *BearerAuthTransport) transport() http.RoundTripper { - if t.Transport != nil { - return t.Transport - } - return http.DefaultTransport -} - -// PATAuthTransport is an http.RoundTripper that authenticates all requests -// using the Personal Access Token specified. -// See here for more info: https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html -type PATAuthTransport struct { - // Token is the key that was provided by Jira when creating the Personal Access Token. - Token string - - // Transport is the underlying HTTP transport to use when making requests. - // It will default to http.DefaultTransport if nil. - Transport http.RoundTripper -} - -// RoundTrip implements the RoundTripper interface. We just add the -// basic auth and return the RoundTripper for this transport type. -func (t *PATAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req2 := cloneRequest(req) // per RoundTripper contract - req2.Header.Set("Authorization", "Bearer "+t.Token) - return t.transport().RoundTrip(req2) -} - -// Client returns an *http.Client that makes requests that are authenticated -// using HTTP Basic Authentication. This is a nice little bit of sugar -// so we can just get the client instead of creating the client in the calling code. -// If it's necessary to send more information on client init, the calling code can -// always skip this and set the transport itself. -func (t *PATAuthTransport) Client() *http.Client { - return &http.Client{Transport: t} -} - -func (t *PATAuthTransport) transport() http.RoundTripper { - if t.Transport != nil { - return t.Transport - } - return http.DefaultTransport -} - -// CookieAuthTransport is an http.RoundTripper that authenticates all requests -// using Jira's cookie-based authentication. -// -// Note that it is generally preferable to use HTTP BASIC authentication with the REST API. -// However, this resource may be used to mimic the behaviour of Jira's log-in page (e.g. to display log-in errors to a user). -// -// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session -type CookieAuthTransport struct { - Username string - Password string - AuthURL string - - // SessionObject is the authenticated cookie string.s - // It's passed in each call to prove the client is authenticated. - SessionObject []*http.Cookie - - // Transport is the underlying HTTP transport to use when making requests. - // It will default to http.DefaultTransport if nil. - Transport http.RoundTripper -} - -// RoundTrip adds the session object to the request. -func (t *CookieAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { - if t.SessionObject == nil { - err := t.setSessionObject() - if err != nil { - return nil, fmt.Errorf("cookieauth: no session object has been set: %w", err) - } - } - - req2 := cloneRequest(req) // per RoundTripper contract - for _, cookie := range t.SessionObject { - // Don't add an empty value cookie to the request - if cookie.Value != "" { - req2.AddCookie(cookie) - } - } - - return t.transport().RoundTrip(req2) -} - -// Client returns an *http.Client that makes requests that are authenticated -// using cookie authentication -func (t *CookieAuthTransport) Client() *http.Client { - return &http.Client{Transport: t} -} - -// setSessionObject attempts to authenticate the user and set -// the session object (e.g. cookie) -func (t *CookieAuthTransport) setSessionObject() error { - req, err := t.buildAuthRequest() - if err != nil { - return err - } - - var authClient = &http.Client{ - Timeout: time.Second * 60, - } - resp, err := authClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - t.SessionObject = resp.Cookies() - return nil -} - -// getAuthRequest assembles the request to get the authenticated cookie -func (t *CookieAuthTransport) buildAuthRequest() (*http.Request, error) { - body := struct { - Username string `json:"username"` - Password string `json:"password"` - }{ - t.Username, - t.Password, - } - - b := new(bytes.Buffer) - json.NewEncoder(b).Encode(body) - - req, err := http.NewRequest("POST", t.AuthURL, b) - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", "application/json") - return req, nil -} - -func (t *CookieAuthTransport) transport() http.RoundTripper { - if t.Transport != nil { - return t.Transport - } - return http.DefaultTransport -} - -// JWTAuthTransport is an http.RoundTripper that authenticates all requests -// using Jira's JWT based authentication. -// -// NOTE: this form of auth should be used by add-ons installed from the Atlassian marketplace. -// -// Jira docs: https://developer.atlassian.com/cloud/jira/platform/understanding-jwt -// Examples in other languages: -// -// https://bitbucket.org/atlassian/atlassian-jwt-ruby/src/d44a8e7a4649e4f23edaa784402655fda7c816ea/lib/atlassian/jwt.rb -// https://bitbucket.org/atlassian/atlassian-jwt-py/src/master/atlassian_jwt/url_utils.py -type JWTAuthTransport struct { - Secret []byte - Issuer string - - // Transport is the underlying HTTP transport to use when making requests. - // It will default to http.DefaultTransport if nil. - Transport http.RoundTripper -} - -func (t *JWTAuthTransport) Client() *http.Client { - return &http.Client{Transport: t} -} - -func (t *JWTAuthTransport) transport() http.RoundTripper { - if t.Transport != nil { - return t.Transport - } - return http.DefaultTransport -} - -// RoundTrip adds the session object to the request. -func (t *JWTAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req2 := cloneRequest(req) // per RoundTripper contract - exp := time.Duration(59) * time.Second - qsh := t.createQueryStringHash(req.Method, req2.URL) - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "iss": t.Issuer, - "iat": time.Now().Unix(), - "exp": time.Now().Add(exp).Unix(), - "qsh": qsh, - }) - - jwtStr, err := token.SignedString(t.Secret) - if err != nil { - return nil, fmt.Errorf("jwtAuth: error signing JWT: %w", err) - } - - req2.Header.Set("Authorization", fmt.Sprintf("JWT %s", jwtStr)) - return t.transport().RoundTrip(req2) -} - -func (t *JWTAuthTransport) createQueryStringHash(httpMethod string, jiraURL *url.URL) string { - canonicalRequest := t.canonicalizeRequest(httpMethod, jiraURL) - h := sha256.Sum256([]byte(canonicalRequest)) - return hex.EncodeToString(h[:]) -} - -func (t *JWTAuthTransport) canonicalizeRequest(httpMethod string, jiraURL *url.URL) string { - path := "/" + strings.Replace(strings.Trim(jiraURL.Path, "/"), "&", "%26", -1) - - var canonicalQueryString []string - for k, v := range jiraURL.Query() { - if k == "jwt" { - continue - } - param := url.QueryEscape(k) - value := url.QueryEscape(strings.Join(v, "")) - canonicalQueryString = append(canonicalQueryString, strings.Replace(strings.Join([]string{param, value}, "="), "+", "%20", -1)) - } - sort.Strings(canonicalQueryString) - return fmt.Sprintf("%s&%s&%s", strings.ToUpper(httpMethod), path, strings.Join(canonicalQueryString, "&")) -} - -// cloneRequest returns a clone of the provided *http.Request. -// The clone is a shallow copy of the struct and its Header map. -func cloneRequest(r *http.Request) *http.Request { - // shallow copy of the struct - r2 := new(http.Request) - *r2 = *r - // deep copy of the Header - r2.Header = make(http.Header, len(r.Header)) - for k, s := range r.Header { - r2.Header[k] = append([]string(nil), s...) - } - return r2 -} diff --git a/jira_test.go b/cloud/jira_test.go similarity index 68% rename from jira_test.go rename to cloud/jira_test.go index 280bfc7..65b2272 100644 --- a/jira_test.go +++ b/cloud/jira_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "bytes" @@ -450,207 +450,3 @@ func TestClient_GetBaseURL_WithURL(t *testing.T) { t.Errorf("Base URLs are not equal. Expected %+v, got %+v", *u, b) } } - -func TestBasicAuthTransport(t *testing.T) { - setup() - defer teardown() - - username, password := "username", "password" - - testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - u, p, ok := r.BasicAuth() - if !ok { - t.Errorf("request does not contain basic auth credentials") - } - if u != username { - t.Errorf("request contained basic auth username %q, want %q", u, username) - } - if p != password { - t.Errorf("request contained basic auth password %q, want %q", p, password) - } - }) - - tp := &BasicAuthTransport{ - Username: username, - Password: password, - } - - basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) - req, _ := basicAuthClient.NewRequest("GET", ".", nil) - basicAuthClient.Do(req, nil) -} - -func TestBasicAuthTransport_transport(t *testing.T) { - // default transport - tp := &BasicAuthTransport{} - if tp.transport() != http.DefaultTransport { - t.Errorf("Expected http.DefaultTransport to be used.") - } - - // custom transport - tp = &BasicAuthTransport{ - Transport: &http.Transport{}, - } - if tp.transport() == http.DefaultTransport { - t.Errorf("Expected custom transport to be used.") - } -} - -// Test that the cookie in the transport is the cookie returned in the header -func TestCookieAuthTransport_SessionObject_Exists(t *testing.T) { - setup() - defer teardown() - - testCookie := &http.Cookie{Name: "test", Value: "test"} - - testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - cookies := r.Cookies() - - if len(cookies) < 1 { - t.Errorf("No cookies set") - } - - if cookies[0].Name != testCookie.Name { - t.Errorf("Cookie names don't match, expected %v, got %v", testCookie.Name, cookies[0].Name) - } - - if cookies[0].Value != testCookie.Value { - t.Errorf("Cookie values don't match, expected %v, got %v", testCookie.Value, cookies[0].Value) - } - }) - - tp := &CookieAuthTransport{ - Username: "username", - Password: "password", - AuthURL: "https://some.jira.com/rest/auth/1/session", - SessionObject: []*http.Cookie{testCookie}, - } - - basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) - req, _ := basicAuthClient.NewRequest("GET", ".", nil) - basicAuthClient.Do(req, nil) -} - -// Test that an empty cookie in the transport is not returned in the header -func TestCookieAuthTransport_SessionObject_ExistsWithEmptyCookie(t *testing.T) { - setup() - defer teardown() - - emptyCookie := &http.Cookie{Name: "empty_cookie", Value: ""} - testCookie := &http.Cookie{Name: "test", Value: "test"} - - testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - cookies := r.Cookies() - - if len(cookies) > 1 { - t.Errorf("The empty cookie should not have been added") - } - - if cookies[0].Name != testCookie.Name { - t.Errorf("Cookie names don't match, expected %v, got %v", testCookie.Name, cookies[0].Name) - } - - if cookies[0].Value != testCookie.Value { - t.Errorf("Cookie values don't match, expected %v, got %v", testCookie.Value, cookies[0].Value) - } - }) - - tp := &CookieAuthTransport{ - Username: "username", - Password: "password", - AuthURL: "https://some.jira.com/rest/auth/1/session", - SessionObject: []*http.Cookie{emptyCookie, testCookie}, - } - - basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) - req, _ := basicAuthClient.NewRequest("GET", ".", nil) - basicAuthClient.Do(req, nil) -} - -// Test that if no cookie is in the transport, it checks for a cookie -func TestCookieAuthTransport_SessionObject_DoesNotExist(t *testing.T) { - setup() - defer teardown() - - testCookie := &http.Cookie{Name: "does_not_exist", Value: "does_not_exist"} - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - http.SetCookie(w, testCookie) - w.Write([]byte(`OK`)) - })) - defer ts.Close() - - testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - cookies := r.Cookies() - - if len(cookies) < 1 { - t.Errorf("No cookies set") - } - - if cookies[0].Name != testCookie.Name { - t.Errorf("Cookie names don't match, expected %v, got %v", testCookie.Name, cookies[0].Name) - } - - if cookies[0].Value != testCookie.Value { - t.Errorf("Cookie values don't match, expected %v, got %v", testCookie.Value, cookies[0].Value) - } - }) - - tp := &CookieAuthTransport{ - Username: "username", - Password: "password", - AuthURL: ts.URL, - } - - basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) - req, _ := basicAuthClient.NewRequest("GET", ".", nil) - basicAuthClient.Do(req, nil) -} - -func TestJWTAuthTransport_HeaderContainsJWT(t *testing.T) { - setup() - defer teardown() - - sharedSecret := []byte("ssshh,it's a secret") - issuer := "add-on.key" - - jwtTransport := &JWTAuthTransport{ - Secret: sharedSecret, - Issuer: issuer, - } - - testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - // look for the presence of the JWT in the header - val := r.Header.Get("Authorization") - if !strings.Contains(val, "JWT ") { - t.Errorf("request does not contain JWT in the Auth header") - } - }) - - jwtClient, _ := NewClient(jwtTransport.Client(), testServer.URL) - jwtClient.Issue.Get("TEST-1", nil) -} - -func TestPATAuthTransport_HeaderContainsAuth(t *testing.T) { - setup() - defer teardown() - - token := "shhh, it's a token" - - patTransport := &PATAuthTransport{ - Token: token, - } - - testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - val := r.Header.Get("Authorization") - expected := "Bearer " + token - if val != expected { - t.Errorf("request does not contain bearer token in the Authorization header.") - } - }) - - client, _ := NewClient(patTransport.Client(), testServer.URL) - client.User.GetSelf() - -} diff --git a/metaissue.go b/cloud/metaissue.go similarity index 99% rename from metaissue.go rename to cloud/metaissue.go index 3953ac7..40f0ae6 100644 --- a/metaissue.go +++ b/cloud/metaissue.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/metaissue_test.go b/cloud/metaissue_test.go similarity index 99% rename from metaissue_test.go rename to cloud/metaissue_test.go index a77951a..15ff751 100644 --- a/metaissue_test.go +++ b/cloud/metaissue_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" diff --git a/organization.go b/cloud/organization.go similarity index 99% rename from organization.go rename to cloud/organization.go index 65f222b..4950f2b 100644 --- a/organization.go +++ b/cloud/organization.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/organization_test.go b/cloud/organization_test.go similarity index 99% rename from organization_test.go rename to cloud/organization_test.go index 1af51ed..ad96d12 100644 --- a/organization_test.go +++ b/cloud/organization_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "encoding/json" diff --git a/permissionscheme.go b/cloud/permissionscheme.go similarity index 99% rename from permissionscheme.go rename to cloud/permissionscheme.go index 7af5a8b..3c11805 100644 --- a/permissionscheme.go +++ b/cloud/permissionscheme.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/permissionschemes_test.go b/cloud/permissionscheme_test.go similarity index 89% rename from permissionschemes_test.go rename to cloud/permissionscheme_test.go index 373e57a..8cddb72 100644 --- a/permissionschemes_test.go +++ b/cloud/permissionscheme_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" @@ -12,7 +12,7 @@ func TestPermissionSchemeService_GetList(t *testing.T) { defer teardown() testAPIEndpoint := "/rest/api/3/permissionscheme" - raw, err := os.ReadFile("./mocks/all_permissionschemes.json") + raw, err := os.ReadFile("../testing/mock-data/all_permissionschemes.json") if err != nil { t.Error(err.Error()) } @@ -40,7 +40,7 @@ func TestPermissionSchemeService_GetList_NoList(t *testing.T) { defer teardown() testAPIEndpoint := "/rest/api/3/permissionscheme" - raw, err := os.ReadFile("./mocks/no_permissionschemes.json") + raw, err := os.ReadFile("../testing/mock-data/no_permissionschemes.json") if err != nil { t.Error(err.Error()) } @@ -63,7 +63,7 @@ func TestPermissionSchemeService_Get(t *testing.T) { setup() defer teardown() testAPIEdpoint := "/rest/api/3/permissionscheme/10100" - raw, err := os.ReadFile("./mocks/permissionscheme.json") + raw, err := os.ReadFile("../testing/mock-data/permissionscheme.json") if err != nil { t.Error(err.Error()) } @@ -86,7 +86,7 @@ func TestPermissionSchemeService_Get_NoScheme(t *testing.T) { setup() defer teardown() testAPIEdpoint := "/rest/api/3/permissionscheme/99999" - raw, err := os.ReadFile("./mocks/no_permissionscheme.json") + raw, err := os.ReadFile("../testing/mock-data/no_permissionscheme.json") if err != nil { t.Error(err.Error()) } diff --git a/priority.go b/cloud/priority.go similarity index 99% rename from priority.go rename to cloud/priority.go index a7b12a4..2cd5971 100644 --- a/priority.go +++ b/cloud/priority.go @@ -1,4 +1,4 @@ -package jira +package cloud import "context" diff --git a/priority_test.go b/cloud/priority_test.go similarity index 87% rename from priority_test.go rename to cloud/priority_test.go index 38258f0..af6305c 100644 --- a/priority_test.go +++ b/cloud/priority_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" @@ -12,7 +12,7 @@ func TestPriorityService_GetList(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/priority" - raw, err := os.ReadFile("./mocks/all_priorities.json") + raw, err := os.ReadFile("../testing/mock-data/all_priorities.json") if err != nil { t.Error(err.Error()) } diff --git a/project.go b/cloud/project.go similarity index 99% rename from project.go rename to cloud/project.go index f1000c8..e67fd02 100644 --- a/project.go +++ b/cloud/project.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/project_test.go b/cloud/project_test.go similarity index 95% rename from project_test.go rename to cloud/project_test.go index 727267b..be0a4a4 100644 --- a/project_test.go +++ b/cloud/project_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" @@ -12,7 +12,7 @@ func TestProjectService_GetList(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/project" - raw, err := os.ReadFile("./mocks/all_projects.json") + raw, err := os.ReadFile("../testing/mock-data/all_projects.json") if err != nil { t.Error(err.Error()) } @@ -36,7 +36,7 @@ func TestProjectService_ListWithOptions(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/project" - raw, err := os.ReadFile("./mocks/all_projects.json") + raw, err := os.ReadFile("../testing/mock-data/all_projects.json") if err != nil { t.Error(err.Error()) } @@ -60,7 +60,7 @@ func TestProjectService_Get(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/project/12310505" - raw, err := os.ReadFile("./mocks/project.json") + raw, err := os.ReadFile("../testing/mock-data/project.json") if err != nil { t.Error(err.Error()) } diff --git a/request.go b/cloud/request.go similarity index 99% rename from request.go rename to cloud/request.go index a933a57..2a0868a 100644 --- a/request.go +++ b/cloud/request.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/request_test.go b/cloud/request_test.go similarity index 99% rename from request_test.go rename to cloud/request_test.go index 89c7361..4585a65 100644 --- a/request_test.go +++ b/cloud/request_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "encoding/json" diff --git a/resolution.go b/cloud/resolution.go similarity index 98% rename from resolution.go rename to cloud/resolution.go index e23d565..df5282e 100644 --- a/resolution.go +++ b/cloud/resolution.go @@ -1,4 +1,4 @@ -package jira +package cloud import "context" diff --git a/resolution_test.go b/cloud/resolution_test.go similarity index 87% rename from resolution_test.go rename to cloud/resolution_test.go index c408631..2ee0487 100644 --- a/resolution_test.go +++ b/cloud/resolution_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" @@ -12,7 +12,7 @@ func TestResolutionService_GetList(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/resolution" - raw, err := os.ReadFile("./mocks/all_resolutions.json") + raw, err := os.ReadFile("../testing/mock-data/all_resolutions.json") if err != nil { t.Error(err.Error()) } diff --git a/role.go b/cloud/role.go similarity index 99% rename from role.go rename to cloud/role.go index 66d223f..6ebc71b 100644 --- a/role.go +++ b/cloud/role.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/role_test.go b/cloud/role_test.go similarity index 89% rename from role_test.go rename to cloud/role_test.go index b1a63b6..88ca0af 100644 --- a/role_test.go +++ b/cloud/role_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" @@ -12,7 +12,7 @@ func TestRoleService_GetList_NoList(t *testing.T) { defer teardown() testAPIEndpoint := "/rest/api/3/role" - raw, err := os.ReadFile("./mocks/no_roles.json") + raw, err := os.ReadFile("../testing/mock-data/no_roles.json") if err != nil { t.Error(err.Error()) } @@ -37,7 +37,7 @@ func TestRoleService_GetList(t *testing.T) { defer teardown() testAPIEndpoint := "/rest/api/3/role" - raw, err := os.ReadFile("./mocks/all_roles.json") + raw, err := os.ReadFile("../testing/mock-data/all_roles.json") if err != nil { t.Error(err.Error()) } @@ -64,7 +64,7 @@ func TestRoleService_Get_NoRole(t *testing.T) { setup() defer teardown() testAPIEdpoint := "/rest/api/3/role/99999" - raw, err := os.ReadFile("./mocks/no_role.json") + raw, err := os.ReadFile("../testing/mock-data/no_role.json") if err != nil { t.Error(err.Error()) } @@ -87,7 +87,7 @@ func TestRoleService_Get(t *testing.T) { setup() defer teardown() testAPIEdpoint := "/rest/api/3/role/10002" - raw, err := os.ReadFile("./mocks/role.json") + raw, err := os.ReadFile("../testing/mock-data/role.json") if err != nil { t.Error(err.Error()) } diff --git a/servicedesk.go b/cloud/servicedesk.go similarity index 99% rename from servicedesk.go rename to cloud/servicedesk.go index f877f86..8ee6c04 100644 --- a/servicedesk.go +++ b/cloud/servicedesk.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/servicedesk_test.go b/cloud/servicedesk_test.go similarity index 99% rename from servicedesk_test.go rename to cloud/servicedesk_test.go index 909ede3..e552705 100644 --- a/servicedesk_test.go +++ b/cloud/servicedesk_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "encoding/json" diff --git a/sprint.go b/cloud/sprint.go similarity index 99% rename from sprint.go rename to cloud/sprint.go index 6d21e4e..e68338c 100644 --- a/sprint.go +++ b/cloud/sprint.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/sprint_test.go b/cloud/sprint_test.go similarity index 98% rename from sprint_test.go rename to cloud/sprint_test.go index 6c8b58a..464d0c0 100644 --- a/sprint_test.go +++ b/cloud/sprint_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "encoding/json" @@ -44,7 +44,7 @@ func TestSprintService_GetIssuesForSprint(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/agile/1.0/sprint/123/issue" - raw, err := os.ReadFile("./mocks/issues_in_sprint.json") + raw, err := os.ReadFile("../testing/mock-data/issues_in_sprint.json") if err != nil { t.Error(err.Error()) } diff --git a/status.go b/cloud/status.go similarity index 99% rename from status.go rename to cloud/status.go index a370392..0480d56 100644 --- a/status.go +++ b/cloud/status.go @@ -1,4 +1,4 @@ -package jira +package cloud import "context" diff --git a/status_test.go b/cloud/status_test.go similarity index 88% rename from status_test.go rename to cloud/status_test.go index 19b1de8..60aa096 100644 --- a/status_test.go +++ b/cloud/status_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" @@ -12,7 +12,7 @@ func TestStatusService_GetAllStatuses(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/status" - raw, err := os.ReadFile("./mocks/all_statuses.json") + raw, err := os.ReadFile("../testing/mock-data/all_statuses.json") if err != nil { t.Error(err.Error()) } diff --git a/statuscategory.go b/cloud/statuscategory.go similarity index 99% rename from statuscategory.go rename to cloud/statuscategory.go index bed5c56..c78b03b 100644 --- a/statuscategory.go +++ b/cloud/statuscategory.go @@ -1,4 +1,4 @@ -package jira +package cloud import "context" diff --git a/statuscategory_test.go b/cloud/statuscategory_test.go similarity index 87% rename from statuscategory_test.go rename to cloud/statuscategory_test.go index f58fc32..9632a0d 100644 --- a/statuscategory_test.go +++ b/cloud/statuscategory_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" @@ -12,7 +12,7 @@ func TestStatusCategoryService_GetList(t *testing.T) { defer teardown() testAPIEdpoint := "/rest/api/2/statuscategory" - raw, err := os.ReadFile("./mocks/all_statuscategories.json") + raw, err := os.ReadFile("../testing/mock-data/all_statuscategories.json") if err != nil { t.Error(err.Error()) } diff --git a/types.go b/cloud/types.go similarity index 92% rename from types.go rename to cloud/types.go index b99fc1c..1dc861c 100644 --- a/types.go +++ b/cloud/types.go @@ -1,4 +1,4 @@ -package jira +package cloud // Bool is a helper routine that allocates a new bool value // to store v and returns a pointer to it. diff --git a/user.go b/cloud/user.go similarity index 99% rename from user.go rename to cloud/user.go index 078b82f..289ef08 100644 --- a/user.go +++ b/cloud/user.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/user_test.go b/cloud/user_test.go similarity index 99% rename from user_test.go rename to cloud/user_test.go index ca572db..f170e8a 100644 --- a/user_test.go +++ b/cloud/user_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" diff --git a/version.go b/cloud/version.go similarity index 99% rename from version.go rename to cloud/version.go index 45eecd5..e10cc89 100644 --- a/version.go +++ b/cloud/version.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "context" diff --git a/version_test.go b/cloud/version_test.go similarity index 99% rename from version_test.go rename to cloud/version_test.go index e00d69e..3e89c11 100644 --- a/version_test.go +++ b/cloud/version_test.go @@ -1,4 +1,4 @@ -package jira +package cloud import ( "fmt" diff --git a/onpremise/README.md b/onpremise/README.md new file mode 100644 index 0000000..3dd3977 --- /dev/null +++ b/onpremise/README.md @@ -0,0 +1,5 @@ +# Jira: On-Premise client + +The API client library for self-hosted Jira instances. + +For further information, please switch to the [README.md in the root folder](../README.md). \ No newline at end of file diff --git a/onpremise/auth_transport.go b/onpremise/auth_transport.go new file mode 100644 index 0000000..f18d1d9 --- /dev/null +++ b/onpremise/auth_transport.go @@ -0,0 +1,17 @@ +package onpremise + +import "net/http" + +// cloneRequest returns a clone of the provided *http.Request. +// The clone is a shallow copy of the struct and its Header map. +func cloneRequest(r *http.Request) *http.Request { + // shallow copy of the struct + r2 := new(http.Request) + *r2 = *r + // deep copy of the Header + r2.Header = make(http.Header, len(r.Header)) + for k, s := range r.Header { + r2.Header[k] = append([]string(nil), s...) + } + return r2 +} diff --git a/onpremise/auth_transport_basic.go b/onpremise/auth_transport_basic.go new file mode 100644 index 0000000..fd638e9 --- /dev/null +++ b/onpremise/auth_transport_basic.go @@ -0,0 +1,39 @@ +package onpremise + +import "net/http" + +// BasicAuthTransport is an http.RoundTripper that authenticates all requests +// using HTTP Basic Authentication with the provided username and password. +type BasicAuthTransport struct { + Username string + Password string + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +// RoundTrip implements the RoundTripper interface. We just add the +// basic auth and return the RoundTripper for this transport type. +func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req2 := cloneRequest(req) // per RoundTripper contract + + req2.SetBasicAuth(t.Username, t.Password) + return t.transport().RoundTrip(req2) +} + +// Client returns an *http.Client that makes requests that are authenticated +// using HTTP Basic Authentication. This is a nice little bit of sugar +// so we can just get the client instead of creating the client in the calling code. +// If it's necessary to send more information on client init, the calling code can +// always skip this and set the transport itself. +func (t *BasicAuthTransport) Client() *http.Client { + return &http.Client{Transport: t} +} + +func (t *BasicAuthTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} diff --git a/onpremise/auth_transport_basic_test.go b/onpremise/auth_transport_basic_test.go new file mode 100644 index 0000000..37918f6 --- /dev/null +++ b/onpremise/auth_transport_basic_test.go @@ -0,0 +1,51 @@ +package onpremise + +import ( + "net/http" + "testing" +) + +func TestBasicAuthTransport(t *testing.T) { + setup() + defer teardown() + + username, password := "username", "password" + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + u, p, ok := r.BasicAuth() + if !ok { + t.Errorf("request does not contain basic auth credentials") + } + if u != username { + t.Errorf("request contained basic auth username %q, want %q", u, username) + } + if p != password { + t.Errorf("request contained basic auth password %q, want %q", p, password) + } + }) + + tp := &BasicAuthTransport{ + Username: username, + Password: password, + } + + basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) + req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient.Do(req, nil) +} + +func TestBasicAuthTransport_transport(t *testing.T) { + // default transport + tp := &BasicAuthTransport{} + if tp.transport() != http.DefaultTransport { + t.Errorf("Expected http.DefaultTransport to be used.") + } + + // custom transport + tp = &BasicAuthTransport{ + Transport: &http.Transport{}, + } + if tp.transport() == http.DefaultTransport { + t.Errorf("Expected custom transport to be used.") + } +} diff --git a/onpremise/auth_transport_bearer.go b/onpremise/auth_transport_bearer.go new file mode 100644 index 0000000..ceb6d65 --- /dev/null +++ b/onpremise/auth_transport_bearer.go @@ -0,0 +1,41 @@ +package onpremise + +import ( + "fmt" + "net/http" +) + +// BearerAuthTransport is a http.RoundTripper that authenticates all requests +// using Jira's bearer (oauth 2.0 (3lo)) based authentication. +type BearerAuthTransport struct { + Token string + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +// RoundTrip implements the RoundTripper interface. We just add the +// bearer token and return the RoundTripper for this transport type. +func (t *BearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req2 := cloneRequest(req) // per RoundTripper contract + + req2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.Token)) + return t.transport().RoundTrip(req2) +} + +// Client returns an *http.Client that makes requests that are authenticated +// using HTTP Basic Authentication. This is a nice little bit of sugar +// so we can just get the client instead of creating the client in the calling code. +// If it's necessary to send more information on client init, the calling code can +// always skip this and set the transport itself. +func (t *BearerAuthTransport) Client() *http.Client { + return &http.Client{Transport: t} +} + +func (t *BearerAuthTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} diff --git a/onpremise/auth_transport_cookie.go b/onpremise/auth_transport_cookie.go new file mode 100644 index 0000000..3b76813 --- /dev/null +++ b/onpremise/auth_transport_cookie.go @@ -0,0 +1,106 @@ +package onpremise + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "time" +) + +// CookieAuthTransport is an http.RoundTripper that authenticates all requests +// using Jira's cookie-based authentication. +// +// Note that it is generally preferable to use HTTP BASIC authentication with the REST API. +// However, this resource may be used to mimic the behaviour of Jira's log-in page (e.g. to display log-in errors to a user). +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session +type CookieAuthTransport struct { + Username string + Password string + AuthURL string + + // SessionObject is the authenticated cookie string.s + // It's passed in each call to prove the client is authenticated. + SessionObject []*http.Cookie + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +// RoundTrip adds the session object to the request. +func (t *CookieAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + if t.SessionObject == nil { + err := t.setSessionObject() + if err != nil { + return nil, fmt.Errorf("cookieauth: no session object has been set: %w", err) + } + } + + req2 := cloneRequest(req) // per RoundTripper contract + for _, cookie := range t.SessionObject { + // Don't add an empty value cookie to the request + if cookie.Value != "" { + req2.AddCookie(cookie) + } + } + + return t.transport().RoundTrip(req2) +} + +// Client returns an *http.Client that makes requests that are authenticated +// using cookie authentication +func (t *CookieAuthTransport) Client() *http.Client { + return &http.Client{Transport: t} +} + +// setSessionObject attempts to authenticate the user and set +// the session object (e.g. cookie) +func (t *CookieAuthTransport) setSessionObject() error { + req, err := t.buildAuthRequest() + if err != nil { + return err + } + + var authClient = &http.Client{ + Timeout: time.Second * 60, + } + resp, err := authClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + t.SessionObject = resp.Cookies() + return nil +} + +// getAuthRequest assembles the request to get the authenticated cookie +func (t *CookieAuthTransport) buildAuthRequest() (*http.Request, error) { + body := struct { + Username string `json:"username"` + Password string `json:"password"` + }{ + t.Username, + t.Password, + } + + b := new(bytes.Buffer) + json.NewEncoder(b).Encode(body) + + req, err := http.NewRequest("POST", t.AuthURL, b) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + return req, nil +} + +func (t *CookieAuthTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} diff --git a/onpremise/auth_transport_cookie_test.go b/onpremise/auth_transport_cookie_test.go new file mode 100644 index 0000000..19a33bc --- /dev/null +++ b/onpremise/auth_transport_cookie_test.go @@ -0,0 +1,119 @@ +package onpremise + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +// Test that the cookie in the transport is the cookie returned in the header +func TestCookieAuthTransport_SessionObject_Exists(t *testing.T) { + setup() + defer teardown() + + testCookie := &http.Cookie{Name: "test", Value: "test"} + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + cookies := r.Cookies() + + if len(cookies) < 1 { + t.Errorf("No cookies set") + } + + if cookies[0].Name != testCookie.Name { + t.Errorf("Cookie names don't match, expected %v, got %v", testCookie.Name, cookies[0].Name) + } + + if cookies[0].Value != testCookie.Value { + t.Errorf("Cookie values don't match, expected %v, got %v", testCookie.Value, cookies[0].Value) + } + }) + + tp := &CookieAuthTransport{ + Username: "username", + Password: "password", + AuthURL: "https://some.jira.com/rest/auth/1/session", + SessionObject: []*http.Cookie{testCookie}, + } + + basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) + req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient.Do(req, nil) +} + +// Test that an empty cookie in the transport is not returned in the header +func TestCookieAuthTransport_SessionObject_ExistsWithEmptyCookie(t *testing.T) { + setup() + defer teardown() + + emptyCookie := &http.Cookie{Name: "empty_cookie", Value: ""} + testCookie := &http.Cookie{Name: "test", Value: "test"} + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + cookies := r.Cookies() + + if len(cookies) > 1 { + t.Errorf("The empty cookie should not have been added") + } + + if cookies[0].Name != testCookie.Name { + t.Errorf("Cookie names don't match, expected %v, got %v", testCookie.Name, cookies[0].Name) + } + + if cookies[0].Value != testCookie.Value { + t.Errorf("Cookie values don't match, expected %v, got %v", testCookie.Value, cookies[0].Value) + } + }) + + tp := &CookieAuthTransport{ + Username: "username", + Password: "password", + AuthURL: "https://some.jira.com/rest/auth/1/session", + SessionObject: []*http.Cookie{emptyCookie, testCookie}, + } + + basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) + req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient.Do(req, nil) +} + +// Test that if no cookie is in the transport, it checks for a cookie +func TestCookieAuthTransport_SessionObject_DoesNotExist(t *testing.T) { + setup() + defer teardown() + + testCookie := &http.Cookie{Name: "does_not_exist", Value: "does_not_exist"} + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + http.SetCookie(w, testCookie) + w.Write([]byte(`OK`)) + })) + defer ts.Close() + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + cookies := r.Cookies() + + if len(cookies) < 1 { + t.Errorf("No cookies set") + } + + if cookies[0].Name != testCookie.Name { + t.Errorf("Cookie names don't match, expected %v, got %v", testCookie.Name, cookies[0].Name) + } + + if cookies[0].Value != testCookie.Value { + t.Errorf("Cookie values don't match, expected %v, got %v", testCookie.Value, cookies[0].Value) + } + }) + + tp := &CookieAuthTransport{ + Username: "username", + Password: "password", + AuthURL: ts.URL, + } + + basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) + req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient.Do(req, nil) +} diff --git a/onpremise/auth_transport_jwt.go b/onpremise/auth_transport_jwt.go new file mode 100644 index 0000000..0cb776b --- /dev/null +++ b/onpremise/auth_transport_jwt.go @@ -0,0 +1,87 @@ +package onpremise + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "net/http" + "net/url" + "sort" + "strings" + "time" + + jwt "github.com/golang-jwt/jwt/v4" +) + +// JWTAuthTransport is an http.RoundTripper that authenticates all requests +// using Jira's JWT based authentication. +// +// NOTE: this form of auth should be used by add-ons installed from the Atlassian marketplace. +// +// Jira docs: https://developer.atlassian.com/cloud/jira/platform/understanding-jwt +// Examples in other languages: +// +// https://bitbucket.org/atlassian/atlassian-jwt-ruby/src/d44a8e7a4649e4f23edaa784402655fda7c816ea/lib/atlassian/jwt.rb +// https://bitbucket.org/atlassian/atlassian-jwt-py/src/master/atlassian_jwt/url_utils.py +type JWTAuthTransport struct { + Secret []byte + Issuer string + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +func (t *JWTAuthTransport) Client() *http.Client { + return &http.Client{Transport: t} +} + +func (t *JWTAuthTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} + +// RoundTrip adds the session object to the request. +func (t *JWTAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req2 := cloneRequest(req) // per RoundTripper contract + exp := time.Duration(59) * time.Second + qsh := t.createQueryStringHash(req.Method, req2.URL) + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "iss": t.Issuer, + "iat": time.Now().Unix(), + "exp": time.Now().Add(exp).Unix(), + "qsh": qsh, + }) + + jwtStr, err := token.SignedString(t.Secret) + if err != nil { + return nil, fmt.Errorf("jwtAuth: error signing JWT: %w", err) + } + + req2.Header.Set("Authorization", fmt.Sprintf("JWT %s", jwtStr)) + return t.transport().RoundTrip(req2) +} + +func (t *JWTAuthTransport) createQueryStringHash(httpMethod string, jiraURL *url.URL) string { + canonicalRequest := t.canonicalizeRequest(httpMethod, jiraURL) + h := sha256.Sum256([]byte(canonicalRequest)) + return hex.EncodeToString(h[:]) +} + +func (t *JWTAuthTransport) canonicalizeRequest(httpMethod string, jiraURL *url.URL) string { + path := "/" + strings.Replace(strings.Trim(jiraURL.Path, "/"), "&", "%26", -1) + + var canonicalQueryString []string + for k, v := range jiraURL.Query() { + if k == "jwt" { + continue + } + param := url.QueryEscape(k) + value := url.QueryEscape(strings.Join(v, "")) + canonicalQueryString = append(canonicalQueryString, strings.Replace(strings.Join([]string{param, value}, "="), "+", "%20", -1)) + } + sort.Strings(canonicalQueryString) + return fmt.Sprintf("%s&%s&%s", strings.ToUpper(httpMethod), path, strings.Join(canonicalQueryString, "&")) +} diff --git a/onpremise/auth_transport_jwt_test.go b/onpremise/auth_transport_jwt_test.go new file mode 100644 index 0000000..ccf308b --- /dev/null +++ b/onpremise/auth_transport_jwt_test.go @@ -0,0 +1,31 @@ +package onpremise + +import ( + "net/http" + "strings" + "testing" +) + +func TestJWTAuthTransport_HeaderContainsJWT(t *testing.T) { + setup() + defer teardown() + + sharedSecret := []byte("ssshh,it's a secret") + issuer := "add-on.key" + + jwtTransport := &JWTAuthTransport{ + Secret: sharedSecret, + Issuer: issuer, + } + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + // look for the presence of the JWT in the header + val := r.Header.Get("Authorization") + if !strings.Contains(val, "JWT ") { + t.Errorf("request does not contain JWT in the Auth header") + } + }) + + jwtClient, _ := NewClient(jwtTransport.Client(), testServer.URL) + jwtClient.Issue.Get("TEST-1", nil) +} diff --git a/onpremise/auth_transport_personal_access_token.go b/onpremise/auth_transport_personal_access_token.go new file mode 100644 index 0000000..e3b3e8d --- /dev/null +++ b/onpremise/auth_transport_personal_access_token.go @@ -0,0 +1,39 @@ +package onpremise + +import "net/http" + +// PATAuthTransport is an http.RoundTripper that authenticates all requests +// using the Personal Access Token specified. +// See here for more info: https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html +type PATAuthTransport struct { + // Token is the key that was provided by Jira when creating the Personal Access Token. + Token string + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +// RoundTrip implements the RoundTripper interface. We just add the +// basic auth and return the RoundTripper for this transport type. +func (t *PATAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req2 := cloneRequest(req) // per RoundTripper contract + req2.Header.Set("Authorization", "Bearer "+t.Token) + return t.transport().RoundTrip(req2) +} + +// Client returns an *http.Client that makes requests that are authenticated +// using HTTP Basic Authentication. This is a nice little bit of sugar +// so we can just get the client instead of creating the client in the calling code. +// If it's necessary to send more information on client init, the calling code can +// always skip this and set the transport itself. +func (t *PATAuthTransport) Client() *http.Client { + return &http.Client{Transport: t} +} + +func (t *PATAuthTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} diff --git a/onpremise/auth_transport_personal_access_token_test.go b/onpremise/auth_transport_personal_access_token_test.go new file mode 100644 index 0000000..b0ac50f --- /dev/null +++ b/onpremise/auth_transport_personal_access_token_test.go @@ -0,0 +1,29 @@ +package onpremise + +import ( + "net/http" + "testing" +) + +func TestPATAuthTransport_HeaderContainsAuth(t *testing.T) { + setup() + defer teardown() + + token := "shhh, it's a token" + + patTransport := &PATAuthTransport{ + Token: token, + } + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + val := r.Header.Get("Authorization") + expected := "Bearer " + token + if val != expected { + t.Errorf("request does not contain bearer token in the Authorization header.") + } + }) + + client, _ := NewClient(patTransport.Client(), testServer.URL) + client.User.GetSelf() + +} diff --git a/onpremise/authentication.go b/onpremise/authentication.go new file mode 100644 index 0000000..4bca28a --- /dev/null +++ b/onpremise/authentication.go @@ -0,0 +1,208 @@ +package onpremise + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" +) + +const ( + // HTTP Basic Authentication + authTypeBasic = 1 + // HTTP Session Authentication + authTypeSession = 2 +) + +// AuthenticationService handles authentication for the Jira instance / API. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#authentication +type AuthenticationService struct { + client *Client + + // Authentication type + authType int + + // Basic auth username + username string + + // Basic auth password + password string +} + +// Session represents a Session JSON response by the Jira API. +type Session struct { + Self string `json:"self,omitempty"` + Name string `json:"name,omitempty"` + Session struct { + Name string `json:"name"` + Value string `json:"value"` + } `json:"session,omitempty"` + LoginInfo struct { + FailedLoginCount int `json:"failedLoginCount"` + LoginCount int `json:"loginCount"` + LastFailedLoginTime string `json:"lastFailedLoginTime"` + PreviousLoginTime string `json:"previousLoginTime"` + } `json:"loginInfo"` + Cookies []*http.Cookie +} + +// AcquireSessionCookieWithContext creates a new session for a user in Jira. +// Once a session has been successfully created it can be used to access any of Jira's remote APIs and also the web UI by passing the appropriate HTTP Cookie header. +// The header will by automatically applied to every API request. +// Note that it is generally preferrable to use HTTP BASIC authentication with the REST API. +// However, this resource may be used to mimic the behaviour of Jira's log-in page (e.g. to display log-in errors to a user). +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session +// +// Deprecated: Use CookieAuthTransport instead +func (s *AuthenticationService) AcquireSessionCookieWithContext(ctx context.Context, username, password string) (bool, error) { + apiEndpoint := "rest/auth/1/session" + body := struct { + Username string `json:"username"` + Password string `json:"password"` + }{ + username, + password, + } + + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, body) + if err != nil { + return false, err + } + + session := new(Session) + resp, err := s.client.Do(req, session) + + if resp != nil { + session.Cookies = resp.Cookies() + } + + if err != nil { + return false, fmt.Errorf("auth at Jira instance failed (HTTP(S) request). %s", err) + } + if resp != nil && resp.StatusCode != 200 { + return false, fmt.Errorf("auth at Jira instance failed (HTTP(S) request). Status code: %d", resp.StatusCode) + } + + s.client.session = session + s.authType = authTypeSession + + return true, nil +} + +// AcquireSessionCookie wraps AcquireSessionCookieWithContext using the background context. +// +// Deprecated: Use CookieAuthTransport instead +func (s *AuthenticationService) AcquireSessionCookie(username, password string) (bool, error) { + return s.AcquireSessionCookieWithContext(context.Background(), username, password) +} + +// SetBasicAuth sets username and password for the basic auth against the Jira instance. +// +// Deprecated: Use BasicAuthTransport instead +func (s *AuthenticationService) SetBasicAuth(username, password string) { + s.username = username + s.password = password + s.authType = authTypeBasic +} + +// Authenticated reports if the current Client has authentication details for Jira +func (s *AuthenticationService) Authenticated() bool { + if s != nil { + if s.authType == authTypeSession { + return s.client.session != nil + } else if s.authType == authTypeBasic { + return s.username != "" + } + + } + return false +} + +// LogoutWithContext logs out the current user that has been authenticated and the session in the client is destroyed. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session +// +// Deprecated: Use CookieAuthTransport to create base client. Logging out is as simple as not using the +// client anymore +func (s *AuthenticationService) LogoutWithContext(ctx context.Context) error { + if s.authType != authTypeSession || s.client.session == nil { + return fmt.Errorf("no user is authenticated") + } + + apiEndpoint := "rest/auth/1/session" + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + if err != nil { + return fmt.Errorf("creating the request to log the user out failed : %s", err) + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return fmt.Errorf("error sending the logout request: %s", err) + } + defer resp.Body.Close() + if resp.StatusCode != 204 { + return fmt.Errorf("the logout was unsuccessful with status %d", resp.StatusCode) + } + + // If logout successful, delete session + s.client.session = nil + + return nil + +} + +// Logout wraps LogoutWithContext using the background context. +// +// Deprecated: Use CookieAuthTransport to create base client. Logging out is as simple as not using the +// client anymore +func (s *AuthenticationService) Logout() error { + return s.LogoutWithContext(context.Background()) +} + +// GetCurrentUserWithContext gets the details of the current user. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session +func (s *AuthenticationService) GetCurrentUserWithContext(ctx context.Context) (*Session, error) { + if s == nil { + return nil, fmt.Errorf("authentication Service is not instantiated") + } + if s.authType != authTypeSession || s.client.session == nil { + return nil, fmt.Errorf("no user is authenticated yet") + } + + apiEndpoint := "rest/auth/1/session" + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, fmt.Errorf("could not create request for getting user info : %s", err) + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return nil, fmt.Errorf("error sending request to get user info : %s", err) + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return nil, fmt.Errorf("getting user info failed with status : %d", resp.StatusCode) + } + ret := new(Session) + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("couldn't read body from the response : %s", err) + } + + err = json.Unmarshal(data, &ret) + + if err != nil { + return nil, fmt.Errorf("could not unmarshall received user info : %s", err) + } + + return ret, nil +} + +// GetCurrentUser wraps GetCurrentUserWithContext using the background context. +func (s *AuthenticationService) GetCurrentUser() (*Session, error) { + return s.GetCurrentUserWithContext(context.Background()) +} diff --git a/onpremise/authentication_test.go b/onpremise/authentication_test.go new file mode 100644 index 0000000..c6c0ee7 --- /dev/null +++ b/onpremise/authentication_test.go @@ -0,0 +1,321 @@ +package onpremise + +import ( + "bytes" + "fmt" + "io" + "net/http" + "reflect" + "testing" +) + +func TestAuthenticationService_AcquireSessionCookie_Failure(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/auth/1/session") + b, err := io.ReadAll(r.Body) + if err != nil { + t.Errorf("Error in read body: %s", err) + } + if !bytes.Contains(b, []byte(`"username":"foo"`)) { + t.Error("No username found") + } + if !bytes.Contains(b, []byte(`"password":"bar"`)) { + t.Error("No password found") + } + + // Emulate error + w.WriteHeader(http.StatusInternalServerError) + }) + + res, err := testClient.Authentication.AcquireSessionCookie("foo", "bar") + if err == nil { + t.Errorf("Expected error, but no error given") + } + if res == true { + t.Error("Expected error, but result was true") + } + + if testClient.Authentication.Authenticated() != false { + t.Error("Expected false, but result was true") + } +} + +func TestAuthenticationService_AcquireSessionCookie_Success(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/auth/1/session") + b, err := io.ReadAll(r.Body) + if err != nil { + t.Errorf("Error in read body: %s", err) + } + if !bytes.Contains(b, []byte(`"username":"foo"`)) { + t.Error("No username found") + } + if !bytes.Contains(b, []byte(`"password":"bar"`)) { + t.Error("No password found") + } + + fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) + }) + + res, err := testClient.Authentication.AcquireSessionCookie("foo", "bar") + if err != nil { + t.Errorf("No error expected. Got %s", err) + } + if res == false { + t.Error("Expected result was true. Got false") + } + + if testClient.Authentication.Authenticated() != true { + t.Error("Expected true, but result was false") + } + + if testClient.Authentication.authType != authTypeSession { + t.Errorf("Expected authType %d. Got %d", authTypeSession, testClient.Authentication.authType) + } +} + +func TestAuthenticationService_SetBasicAuth(t *testing.T) { + setup() + defer teardown() + + testClient.Authentication.SetBasicAuth("test-user", "test-password") + + if testClient.Authentication.username != "test-user" { + t.Errorf("Expected username test-user. Got %s", testClient.Authentication.username) + } + + if testClient.Authentication.password != "test-password" { + t.Errorf("Expected password test-password. Got %s", testClient.Authentication.password) + } + + if testClient.Authentication.authType != authTypeBasic { + t.Errorf("Expected authType %d. Got %d", authTypeBasic, testClient.Authentication.authType) + } +} + +func TestAuthenticationService_Authenticated(t *testing.T) { + // Skip setup() because we don't want a fully setup client + testClient = new(Client) + + // Test before we've attempted to authenticate + if testClient.Authentication.Authenticated() != false { + t.Error("Expected false, but result was true") + } +} + +func TestAuthenticationService_Authenticated_WithBasicAuth(t *testing.T) { + setup() + defer teardown() + + testClient.Authentication.SetBasicAuth("test-user", "test-password") + + // Test before we've attempted to authenticate + if testClient.Authentication.Authenticated() != true { + t.Error("Expected true, but result was false") + } +} + +func TestAuthenticationService_Authenticated_WithBasicAuthButNoUsername(t *testing.T) { + setup() + defer teardown() + + testClient.Authentication.SetBasicAuth("", "test-password") + + // Test before we've attempted to authenticate + if testClient.Authentication.Authenticated() != false { + t.Error("Expected false, but result was true") + } +} + +func TestAuthenticationService_GetUserInfo_AccessForbidden_Fail(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/auth/1/session") + b, err := io.ReadAll(r.Body) + if err != nil { + t.Errorf("Error in read body: %s", err) + } + if !bytes.Contains(b, []byte(`"username":"foo"`)) { + t.Error("No username found") + } + if !bytes.Contains(b, []byte(`"password":"bar"`)) { + t.Error("No password found") + } + + fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) + } + + if r.Method == "GET" { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/auth/1/session") + + w.WriteHeader(http.StatusForbidden) + } + }) + + testClient.Authentication.AcquireSessionCookie("foo", "bar") + + _, err := testClient.Authentication.GetCurrentUser() + if err == nil { + t.Errorf("Non nil error expect, received nil") + } +} + +func TestAuthenticationService_GetUserInfo_NonOkStatusCode_Fail(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/auth/1/session") + b, err := io.ReadAll(r.Body) + if err != nil { + t.Errorf("Error in read body: %s", err) + } + if !bytes.Contains(b, []byte(`"username":"foo"`)) { + t.Error("No username found") + } + if !bytes.Contains(b, []byte(`"password":"bar"`)) { + t.Error("No password found") + } + + fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) + } + + if r.Method == "GET" { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/auth/1/session") + //any status but 200 + w.WriteHeader(240) + } + }) + + testClient.Authentication.AcquireSessionCookie("foo", "bar") + + _, err := testClient.Authentication.GetCurrentUser() + if err == nil { + t.Errorf("Non nil error expect, received nil") + } +} + +func TestAuthenticationService_GetUserInfo_FailWithoutLogin(t *testing.T) { + // no setup() required here + testClient = new(Client) + + _, err := testClient.Authentication.GetCurrentUser() + if err == nil { + t.Errorf("Expected error, but got %s", err) + } +} + +func TestAuthenticationService_GetUserInfo_Success(t *testing.T) { + setup() + defer teardown() + + testUserInfo := new(Session) + testUserInfo.Name = "foo" + testUserInfo.Self = "https://my.jira.com/rest/api/latest/user?username=foo" + testUserInfo.LoginInfo.FailedLoginCount = 12 + testUserInfo.LoginInfo.LastFailedLoginTime = "2016-09-06T16:41:23.949+0200" + testUserInfo.LoginInfo.LoginCount = 357 + testUserInfo.LoginInfo.PreviousLoginTime = "2016-09-07T11:36:23.476+0200" + + testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/auth/1/session") + b, err := io.ReadAll(r.Body) + if err != nil { + t.Errorf("Error in read body: %s", err) + } + if !bytes.Contains(b, []byte(`"username":"foo"`)) { + t.Error("No username found") + } + if !bytes.Contains(b, []byte(`"password":"bar"`)) { + t.Error("No password found") + } + + fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) + } + + if r.Method == "GET" { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/auth/1/session") + fmt.Fprint(w, `{"self":"https://my.jira.com/rest/api/latest/user?username=foo","name":"foo","loginInfo":{"failedLoginCount":12,"loginCount":357,"lastFailedLoginTime":"2016-09-06T16:41:23.949+0200","previousLoginTime":"2016-09-07T11:36:23.476+0200"}}`) + } + }) + + testClient.Authentication.AcquireSessionCookie("foo", "bar") + + userinfo, err := testClient.Authentication.GetCurrentUser() + if err != nil { + t.Errorf("Nil error expect, received %s", err) + } + equal := reflect.DeepEqual(*testUserInfo, *userinfo) + + if !equal { + t.Error("The user information doesn't match") + } +} + +func TestAuthenticationService_Logout_Success(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/auth/1/session") + b, err := io.ReadAll(r.Body) + if err != nil { + t.Errorf("Error in read body: %s", err) + } + if !bytes.Contains(b, []byte(`"username":"foo"`)) { + t.Error("No username found") + } + if !bytes.Contains(b, []byte(`"password":"bar"`)) { + t.Error("No password found") + } + + fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) + } + + if r.Method == "DELETE" { + // return 204 + w.WriteHeader(http.StatusNoContent) + } + }) + + testClient.Authentication.AcquireSessionCookie("foo", "bar") + + err := testClient.Authentication.Logout() + if err != nil { + t.Errorf("Expected nil error, got %s", err) + } +} + +func TestAuthenticationService_Logout_FailWithoutLogin(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { + if r.Method == "DELETE" { + // 401 + w.WriteHeader(http.StatusUnauthorized) + } + }) + err := testClient.Authentication.Logout() + if err == nil { + t.Error("Expected not nil, got nil") + } +} diff --git a/onpremise/board.go b/onpremise/board.go new file mode 100644 index 0000000..52cbfa6 --- /dev/null +++ b/onpremise/board.go @@ -0,0 +1,315 @@ +package onpremise + +import ( + "context" + "fmt" + "strconv" + "time" +) + +// BoardService handles Agile Boards for the Jira instance / API. +// +// Jira API docs: https://docs.atlassian.com/jira-software/REST/server/ +type BoardService struct { + client *Client +} + +// BoardsList reflects a list of agile boards +type BoardsList struct { + MaxResults int `json:"maxResults" structs:"maxResults"` + StartAt int `json:"startAt" structs:"startAt"` + Total int `json:"total" structs:"total"` + IsLast bool `json:"isLast" structs:"isLast"` + Values []Board `json:"values" structs:"values"` +} + +// Board represents a Jira agile board +type Board struct { + ID int `json:"id,omitempty" structs:"id,omitempty"` + Self string `json:"self,omitempty" structs:"self,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitemtpy"` + Type string `json:"type,omitempty" structs:"type,omitempty"` + FilterID int `json:"filterId,omitempty" structs:"filterId,omitempty"` +} + +// BoardListOptions specifies the optional parameters to the BoardService.GetList +type BoardListOptions struct { + // BoardType filters results to boards of the specified type. + // Valid values: scrum, kanban. + BoardType string `url:"type,omitempty"` + // Name filters results to boards that match or partially match the specified name. + Name string `url:"name,omitempty"` + // ProjectKeyOrID filters results to boards that are relevant to a project. + // Relevance meaning that the JQL filter defined in board contains a reference to a project. + ProjectKeyOrID string `url:"projectKeyOrId,omitempty"` + + SearchOptions +} + +// GetAllSprintsOptions specifies the optional parameters to the BoardService.GetList +type GetAllSprintsOptions struct { + // State filters results to sprints in the specified states, comma-separate list + State string `url:"state,omitempty"` + + SearchOptions +} + +// SprintsList reflects a list of agile sprints +type SprintsList struct { + MaxResults int `json:"maxResults" structs:"maxResults"` + StartAt int `json:"startAt" structs:"startAt"` + Total int `json:"total" structs:"total"` + IsLast bool `json:"isLast" structs:"isLast"` + Values []Sprint `json:"values" structs:"values"` +} + +// Sprint represents a sprint on Jira agile board +type Sprint struct { + ID int `json:"id" structs:"id"` + Name string `json:"name" structs:"name"` + CompleteDate *time.Time `json:"completeDate" structs:"completeDate"` + EndDate *time.Time `json:"endDate" structs:"endDate"` + StartDate *time.Time `json:"startDate" structs:"startDate"` + OriginBoardID int `json:"originBoardId" structs:"originBoardId"` + Self string `json:"self" structs:"self"` + State string `json:"state" structs:"state"` +} + +// BoardConfiguration represents a boardConfiguration of a jira board +type BoardConfiguration struct { + ID int `json:"id"` + Name string `json:"name"` + Self string `json:"self"` + Location BoardConfigurationLocation `json:"location"` + Filter BoardConfigurationFilter `json:"filter"` + SubQuery BoardConfigurationSubQuery `json:"subQuery"` + ColumnConfig BoardConfigurationColumnConfig `json:"columnConfig"` +} + +// BoardConfigurationFilter reference to the filter used by the given board. +type BoardConfigurationFilter struct { + ID string `json:"id"` + Self string `json:"self"` +} + +// BoardConfigurationSubQuery (Kanban only) - JQL subquery used by the given board. +type BoardConfigurationSubQuery struct { + Query string `json:"query"` +} + +// BoardConfigurationLocation reference to the container that the board is located in +type BoardConfigurationLocation struct { + Type string `json:"type"` + Key string `json:"key"` + ID string `json:"id"` + Self string `json:"self"` + Name string `json:"name"` +} + +// BoardConfigurationColumnConfig lists the columns for a given board in the order defined in the column configuration +// with constrainttype (none, issueCount, issueCountExclSubs) +type BoardConfigurationColumnConfig struct { + Columns []BoardConfigurationColumn `json:"columns"` + ConstraintType string `json:"constraintType"` +} + +// BoardConfigurationColumn lists the name of the board with the statuses that maps to a particular column +type BoardConfigurationColumn struct { + Name string `json:"name"` + Status []BoardConfigurationColumnStatus `json:"statuses"` + Min int `json:"min,omitempty"` + Max int `json:"max,omitempty"` +} + +// BoardConfigurationColumnStatus represents a status in the column configuration +type BoardConfigurationColumnStatus struct { + ID string `json:"id"` + Self string `json:"self"` +} + +// GetAllBoardsWithContext will returns all boards. This only includes boards that the user has permission to view. +// +// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getAllBoards +func (s *BoardService) GetAllBoardsWithContext(ctx context.Context, opt *BoardListOptions) (*BoardsList, *Response, error) { + apiEndpoint := "rest/agile/1.0/board" + url, err := addOptions(apiEndpoint, opt) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, nil, err + } + + boards := new(BoardsList) + resp, err := s.client.Do(req, boards) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return boards, resp, err +} + +// GetAllBoards wraps GetAllBoardsWithContext using the background context. +func (s *BoardService) GetAllBoards(opt *BoardListOptions) (*BoardsList, *Response, error) { + return s.GetAllBoardsWithContext(context.Background(), opt) +} + +// GetBoardWithContext will returns the board for the given boardID. +// This board will only be returned if the user has permission to view it. +// +// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard +func (s *BoardService) GetBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + board := new(Board) + resp, err := s.client.Do(req, board) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return board, resp, nil +} + +// GetBoard wraps GetBoardWithContext using the background context. +func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) { + return s.GetBoardWithContext(context.Background(), boardID) +} + +// CreateBoardWithContext creates a new board. Board name, type and filter Id is required. +// name - Must be less than 255 characters. +// type - Valid values: scrum, kanban +// filterId - Id of a filter that the user has permissions to view. +// Note, if the user does not have the 'Create shared objects' permission and tries to create a shared board, a private +// board will be created instead (remember that board sharing depends on the filter sharing). +// +// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard +func (s *BoardService) CreateBoardWithContext(ctx context.Context, board *Board) (*Board, *Response, error) { + apiEndpoint := "rest/agile/1.0/board" + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, board) + if err != nil { + return nil, nil, err + } + + responseBoard := new(Board) + resp, err := s.client.Do(req, responseBoard) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return responseBoard, resp, nil +} + +// CreateBoard wraps CreateBoardWithContext using the background context. +func (s *BoardService) CreateBoard(board *Board) (*Board, *Response, error) { + return s.CreateBoardWithContext(context.Background(), board) +} + +// DeleteBoardWithContext will delete an agile board. +// +// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-deleteBoard +// Caller must close resp.Body +func (s *BoardService) DeleteBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + err = NewJiraError(resp, err) + } + return nil, resp, err +} + +// DeleteBoard wraps DeleteBoardWithContext using the background context. +// Caller must close resp.Body +func (s *BoardService) DeleteBoard(boardID int) (*Board, *Response, error) { + return s.DeleteBoardWithContext(context.Background(), boardID) +} + +// GetAllSprintsWithContext will return all sprints from a board, for a given board Id. +// This only includes sprints that the user has permission to view. +// +// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint +func (s *BoardService) GetAllSprintsWithContext(ctx context.Context, boardID string) ([]Sprint, *Response, error) { + id, err := strconv.Atoi(boardID) + if err != nil { + return nil, nil, err + } + + result, response, err := s.GetAllSprintsWithOptions(id, &GetAllSprintsOptions{}) + if err != nil { + return nil, nil, err + } + + return result.Values, response, nil +} + +// GetAllSprints wraps GetAllSprintsWithContext using the background context. +func (s *BoardService) GetAllSprints(boardID string) ([]Sprint, *Response, error) { + return s.GetAllSprintsWithContext(context.Background(), boardID) +} + +// GetAllSprintsWithOptionsWithContext will return sprints from a board, for a given board Id and filtering options +// This only includes sprints that the user has permission to view. +// +// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint +func (s *BoardService) GetAllSprintsWithOptionsWithContext(ctx context.Context, boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/sprint", boardID) + url, err := addOptions(apiEndpoint, options) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, nil, err + } + + result := new(SprintsList) + resp, err := s.client.Do(req, result) + if err != nil { + err = NewJiraError(resp, err) + } + + return result, resp, err +} + +// GetAllSprintsWithOptions wraps GetAllSprintsWithOptionsWithContext using the background context. +func (s *BoardService) GetAllSprintsWithOptions(boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { + return s.GetAllSprintsWithOptionsWithContext(context.Background(), boardID, options) +} + +// GetBoardConfigurationWithContext will return a board configuration for a given board Id +// Jira API docs:https://developer.atlassian.com/cloud/jira/software/rest/#api-rest-agile-1-0-board-boardId-configuration-get +func (s *BoardService) GetBoardConfigurationWithContext(ctx context.Context, boardID int) (*BoardConfiguration, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/configuration", boardID) + + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + + if err != nil { + return nil, nil, err + } + + result := new(BoardConfiguration) + resp, err := s.client.Do(req, result) + if err != nil { + err = NewJiraError(resp, err) + } + + return result, resp, err + +} + +// GetBoardConfiguration wraps GetBoardConfigurationWithContext using the background context. +func (s *BoardService) GetBoardConfiguration(boardID int) (*BoardConfiguration, *Response, error) { + return s.GetBoardConfigurationWithContext(context.Background(), boardID) +} diff --git a/onpremise/board_test.go b/onpremise/board_test.go new file mode 100644 index 0000000..ccdcae8 --- /dev/null +++ b/onpremise/board_test.go @@ -0,0 +1,266 @@ +package onpremise + +import ( + "fmt" + "net/http" + "os" + "testing" +) + +func TestBoardService_GetAllBoards(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/agile/1.0/board" + + raw, err := os.ReadFile("../testing/mock-data/all_boards.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, string(raw)) + }) + + projects, _, err := testClient.Board.GetAllBoards(nil) + if projects == nil { + t.Error("Expected boards list. Boards list is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +// Test with params +func TestBoardService_GetAllBoards_WithFilter(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/agile/1.0/board" + + raw, err := os.ReadFile("../testing/mock-data/all_boards_filtered.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + testRequestParams(t, r, map[string]string{"type": "scrum", "name": "Test", "startAt": "1", "maxResults": "10", "projectKeyOrId": "TE"}) + fmt.Fprint(w, string(raw)) + }) + + boardsListOptions := &BoardListOptions{ + BoardType: "scrum", + Name: "Test", + ProjectKeyOrID: "TE", + } + boardsListOptions.StartAt = 1 + boardsListOptions.MaxResults = 10 + + projects, _, err := testClient.Board.GetAllBoards(boardsListOptions) + if projects == nil { + t.Error("Expected boards list. Boards list is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestBoardService_GetBoard(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/agile/1.0/board/1" + + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, `{"id":4,"self":"https://test.jira.org/rest/agile/1.0/board/1","name":"Test Weekly","type":"scrum"}`) + }) + + board, _, err := testClient.Board.GetBoard(1) + if board == nil { + t.Error("Expected board list. Board list is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestBoardService_GetBoard_WrongID(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/2/board/99999999" + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, nil) + }) + + board, resp, err := testClient.Board.GetBoard(99999999) + if board != nil { + t.Errorf("Expected nil. Got %s", err) + } + + if resp.Status == "404" { + t.Errorf("Expected status 404. Got %s", resp.Status) + } + if err == nil { + t.Errorf("Error given: %s", err) + } +} + +func TestBoardService_CreateBoard(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/agile/1.0/board", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/agile/1.0/board") + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"id":17,"self":"https://test.jira.org/rest/agile/1.0/board/17","name":"Test","type":"kanban"}`) + }) + + b := &Board{ + Name: "Test", + Type: "kanban", + FilterID: 17, + } + issue, _, err := testClient.Board.CreateBoard(b) + if issue == nil { + t.Error("Expected board. Board is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestBoardService_DeleteBoard(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/agile/1.0/board/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, "/rest/agile/1.0/board/1") + + w.WriteHeader(http.StatusNoContent) + fmt.Fprint(w, `{}`) + }) + + _, resp, err := testClient.Board.DeleteBoard(1) + if resp.StatusCode != 204 { + t.Error("Expected board not deleted.") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestBoardService_GetAllSprints(t *testing.T) { + setup() + defer teardown() + + testAPIEndpoint := "/rest/agile/1.0/board/123/sprint" + + raw, err := os.ReadFile("../testing/mock-data/sprints.json") + if err != nil { + t.Error(err.Error()) + } + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + sprints, _, err := testClient.Board.GetAllSprints("123") + + if err != nil { + t.Errorf("Got error: %v", err) + } + + if sprints == nil { + t.Error("Expected sprint list. Got nil.") + } + + if len(sprints) != 4 { + t.Errorf("Expected 4 transitions. Got %d", len(sprints)) + } +} + +func TestBoardService_GetAllSprintsWithOptions(t *testing.T) { + setup() + defer teardown() + + testAPIEndpoint := "/rest/agile/1.0/board/123/sprint" + + raw, err := os.ReadFile("../testing/mock-data/sprints_filtered.json") + if err != nil { + t.Error(err.Error()) + } + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + sprints, _, err := testClient.Board.GetAllSprintsWithOptions(123, &GetAllSprintsOptions{State: "active,future"}) + if err != nil { + t.Errorf("Got error: %v", err) + } + + if sprints == nil { + t.Error("Expected sprint list. Got nil.") + return + } + + if len(sprints.Values) != 1 { + t.Errorf("Expected 1 transition. Got %d", len(sprints.Values)) + } +} + +func TestBoardService_GetBoardConfigoration(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/agile/1.0/board/35/configuration" + + raw, err := os.ReadFile("../testing/mock-data/board_configuration.json") + if err != nil { + t.Error(err.Error()) + } + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + boardConfiguration, _, err := testClient.Board.GetBoardConfiguration(35) + if err != nil { + t.Errorf("Got error: %v", err) + } + + if boardConfiguration == nil { + t.Error("Expected boardConfiguration. Got nil.") + return + } + + if len(boardConfiguration.ColumnConfig.Columns) != 6 { + t.Errorf("Expected 6 columns. go %d", len(boardConfiguration.ColumnConfig.Columns)) + } + + backlogColumn := boardConfiguration.ColumnConfig.Columns[0] + if backlogColumn.Min != 5 { + t.Errorf("Expected a min of 5 issues in backlog. Got %d", backlogColumn.Min) + } + if backlogColumn.Max != 30 { + t.Errorf("Expected a max of 30 issues in backlog. Got %d", backlogColumn.Max) + } + + inProgressColumn := boardConfiguration.ColumnConfig.Columns[2] + if inProgressColumn.Min != 0 { + t.Errorf("Expected a min of 0 issues in progress. Got %d", inProgressColumn.Min) + } + if inProgressColumn.Max != 0 { + t.Errorf("Expected a max of 0 issues in progress. Got %d", inProgressColumn.Max) + } +} diff --git a/onpremise/component.go b/onpremise/component.go new file mode 100644 index 0000000..7c918dd --- /dev/null +++ b/onpremise/component.go @@ -0,0 +1,44 @@ +package onpremise + +import "context" + +// ComponentService handles components for the Jira instance / API.// +// Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.1/#api/2/component +type ComponentService struct { + client *Client +} + +// CreateComponentOptions are passed to the ComponentService.Create function to create a new Jira component +type CreateComponentOptions struct { + Name string `json:"name,omitempty" structs:"name,omitempty"` + Description string `json:"description,omitempty" structs:"description,omitempty"` + Lead *User `json:"lead,omitempty" structs:"lead,omitempty"` + LeadUserName string `json:"leadUserName,omitempty" structs:"leadUserName,omitempty"` + AssigneeType string `json:"assigneeType,omitempty" structs:"assigneeType,omitempty"` + Assignee *User `json:"assignee,omitempty" structs:"assignee,omitempty"` + Project string `json:"project,omitempty" structs:"project,omitempty"` + ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"` +} + +// CreateWithContext creates a new Jira component based on the given options. +func (s *ComponentService) CreateWithContext(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) { + apiEndpoint := "rest/api/2/component" + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, options) + if err != nil { + return nil, nil, err + } + + component := new(ProjectComponent) + resp, err := s.client.Do(req, component) + + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + + return component, resp, nil +} + +// Create wraps CreateWithContext using the background context. +func (s *ComponentService) Create(options *CreateComponentOptions) (*ProjectComponent, *Response, error) { + return s.CreateWithContext(context.Background(), options) +} diff --git a/onpremise/component_test.go b/onpremise/component_test.go new file mode 100644 index 0000000..bc60e09 --- /dev/null +++ b/onpremise/component_test.go @@ -0,0 +1,29 @@ +package onpremise + +import ( + "fmt" + "net/http" + "testing" +) + +func TestComponentService_Create_Success(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/component", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/component") + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{ "self": "http://www.example.com/jira/rest/api/2/component/10000", "id": "10000", "name": "Component 1", "description": "This is a Jira component", "lead": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "avatarUrls": { "48x48": "http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", "24x24": "http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred", "16x16": "http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", "32x32": "http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred" }, "displayName": "Fred F. User", "active": false }, "assigneeType": "PROJECT_LEAD", "assignee": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "avatarUrls": { "48x48": "http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", "24x24": "http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred", "16x16": "http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", "32x32": "http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred" }, "displayName": "Fred F. User", "active": false }, "realAssigneeType": "PROJECT_LEAD", "realAssignee": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "avatarUrls": { "48x48": "http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", "24x24": "http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred", "16x16": "http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", "32x32": "http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred" }, "displayName": "Fred F. User", "active": false }, "isAssigneeTypeValid": false, "project": "HSP", "projectId": 10000 }`) + }) + + component, _, err := testClient.Component.Create(&CreateComponentOptions{ + Name: "foo-bar", + }) + if component == nil { + t.Error("Expected component. Component is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} diff --git a/onpremise/customer.go b/onpremise/customer.go new file mode 100644 index 0000000..82a46a0 --- /dev/null +++ b/onpremise/customer.go @@ -0,0 +1,72 @@ +package onpremise + +import ( + "context" + "net/http" +) + +// CustomerService handles ServiceDesk customers for the Jira instance / API. +type CustomerService struct { + client *Client +} + +// Customer represents a ServiceDesk customer. +type Customer struct { + AccountID string `json:"accountId,omitempty" structs:"accountId,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + Key string `json:"key,omitempty" structs:"key,omitempty"` + EmailAddress string `json:"emailAddress,omitempty" structs:"emailAddress,omitempty"` + DisplayName string `json:"displayName,omitempty" structs:"displayName,omitempty"` + Active *bool `json:"active,omitempty" structs:"active,omitempty"` + TimeZone string `json:"timeZone,omitempty" structs:"timeZone,omitempty"` + Links *SelfLink `json:"_links,omitempty" structs:"_links,omitempty"` +} + +// CustomerListOptions is the query options for listing customers. +type CustomerListOptions struct { + Query string `url:"query,omitempty"` + Start int `url:"start,omitempty"` + Limit int `url:"limit,omitempty"` +} + +// CustomerList is a page of customers. +type CustomerList struct { + Values []Customer `json:"values,omitempty" structs:"values,omitempty"` + Start int `json:"start,omitempty" structs:"start,omitempty"` + Limit int `json:"limit,omitempty" structs:"limit,omitempty"` + IsLast bool `json:"isLastPage,omitempty" structs:"isLastPage,omitempty"` + Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"` +} + +// CreateWithContext creates a ServiceDesk customer. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-customer/#api-rest-servicedeskapi-customer-post +func (c *CustomerService) CreateWithContext(ctx context.Context, email, displayName string) (*Customer, *Response, error) { + const apiEndpoint = "rest/servicedeskapi/customer" + + payload := struct { + Email string `json:"email"` + DisplayName string `json:"displayName"` + }{ + Email: email, + DisplayName: displayName, + } + + req, err := c.client.NewRequestWithContext(ctx, http.MethodPost, apiEndpoint, payload) + if err != nil { + return nil, nil, err + } + + responseCustomer := new(Customer) + resp, err := c.client.Do(req, responseCustomer) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + + return responseCustomer, resp, nil +} + +// Create wraps CreateWithContext using the background context. +func (c *CustomerService) Create(email, displayName string) (*Customer, *Response, error) { + return c.CreateWithContext(context.Background(), email, displayName) +} diff --git a/onpremise/customer_test.go b/onpremise/customer_test.go new file mode 100644 index 0000000..db88657 --- /dev/null +++ b/onpremise/customer_test.go @@ -0,0 +1,56 @@ +package onpremise + +import ( + "fmt" + "net/http" + "testing" +) + +func TestCustomerService_Create(t *testing.T) { + setup() + defer teardown() + + const ( + wantDisplayName = "Fred F. User" + wantEmailAddress = "fred@example.com" + ) + + testMux.HandleFunc("/rest/servicedeskapi/customer", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/servicedeskapi/customer") + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, `{ + "accountId": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "name": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "key": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "emailAddress": "%s", + "displayName": "%s", + "active": true, + "timeZone": "Australia/Sydney", + "_links": { + "jiraRest": "https://your-domain.atlassian.net/rest/api/2/user?username=qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "avatarUrls": { + "48x48": "https://avatar-cdn.atlassian.com/image", + "24x24": "https://avatar-cdn.atlassian.com/image", + "16x16": "https://avatar-cdn.atlassian.com/image", + "32x32": "https://avatar-cdn.atlassian.com/image" + }, + "self": "https://your-domain.atlassian.net/rest/api/2/user?username=qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b" + } + }`, wantEmailAddress, wantDisplayName) + }) + + gotCustomer, _, err := testClient.Customer.Create(wantEmailAddress, wantDisplayName) + if err != nil { + t.Fatal(err) + } + + if want, got := wantDisplayName, gotCustomer.DisplayName; want != got { + t.Fatalf("want display name: %q, got %q", want, got) + } + + if want, got := wantEmailAddress, gotCustomer.EmailAddress; want != got { + t.Fatalf("want email address: %q, got %q", want, got) + } +} diff --git a/onpremise/error.go b/onpremise/error.go new file mode 100644 index 0000000..3894890 --- /dev/null +++ b/onpremise/error.go @@ -0,0 +1,87 @@ +package onpremise + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "strings" +) + +// Error message from Jira +// See https://docs.atlassian.com/jira/REST/cloud/#error-responses +type Error struct { + HTTPError error + ErrorMessages []string `json:"errorMessages"` + Errors map[string]string `json:"errors"` +} + +// NewJiraError creates a new jira Error +func NewJiraError(resp *Response, httpError error) error { + if resp == nil { + return fmt.Errorf("no response returned: %w", httpError) + } + + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("%s: %w", httpError.Error(), err) + } + jerr := Error{HTTPError: httpError} + contentType := resp.Header.Get("Content-Type") + if strings.HasPrefix(contentType, "application/json") { + err = json.Unmarshal(body, &jerr) + if err != nil { + return fmt.Errorf("%s: could not parse JSON: %w", httpError.Error(), err) + } + } else { + if httpError == nil { + return fmt.Errorf("got response status %s:%s", resp.Status, string(body)) + } + return fmt.Errorf("%s: %s: %w", resp.Status, string(body), httpError) + } + + return &jerr +} + +// Error is a short string representing the error +func (e *Error) Error() string { + if len(e.ErrorMessages) > 0 { + // return fmt.Sprintf("%v", e.HTTPError) + return fmt.Sprintf("%s: %v", e.ErrorMessages[0], e.HTTPError) + } + if len(e.Errors) > 0 { + for key, value := range e.Errors { + return fmt.Sprintf("%s - %s: %v", key, value, e.HTTPError) + } + } + return e.HTTPError.Error() +} + +// LongError is a full representation of the error as a string +func (e *Error) LongError() string { + var msg bytes.Buffer + if e.HTTPError != nil { + msg.WriteString("Original:\n") + msg.WriteString(e.HTTPError.Error()) + msg.WriteString("\n") + } + if len(e.ErrorMessages) > 0 { + msg.WriteString("Messages:\n") + for _, v := range e.ErrorMessages { + msg.WriteString(" - ") + msg.WriteString(v) + msg.WriteString("\n") + } + } + if len(e.Errors) > 0 { + for key, value := range e.Errors { + msg.WriteString(" - ") + msg.WriteString(key) + msg.WriteString(" - ") + msg.WriteString(value) + msg.WriteString("\n") + } + } + return msg.String() +} diff --git a/onpremise/error_test.go b/onpremise/error_test.go new file mode 100644 index 0000000..4238f64 --- /dev/null +++ b/onpremise/error_test.go @@ -0,0 +1,205 @@ +package onpremise + +import ( + "errors" + "fmt" + "net/http" + "strings" + "testing" +) + +func TestError_NewJiraError(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, `{"errorMessages":["Issue does not exist or you do not have permission to see it."],"errors":{}}`) + }) + + req, _ := testClient.NewRequest("GET", "/", nil) + resp, _ := testClient.Do(req, nil) + + err := NewJiraError(resp, errors.New("Original http error")) + if err, ok := err.(*Error); !ok { + t.Errorf("Expected jira Error. Got %s", err.Error()) + } + + if !strings.Contains(err.Error(), "Issue does not exist") { + t.Errorf("Expected issue message. Got: %s", err.Error()) + } +} + +func TestError_NoResponse(t *testing.T) { + err := NewJiraError(nil, errors.New("Original http error")) + + msg := err.Error() + if !strings.Contains(msg, "Original http error") { + t.Errorf("Expected the original error message: Got\n%s\n", msg) + } + + if !strings.Contains(msg, "no response returned") { + t.Errorf("Expected the 'no response returned' error message: Got\n%s\n", msg) + } +} + +func TestError_NoJSON(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, `Original message body`) + }) + + req, _ := testClient.NewRequest("GET", "/", nil) + resp, _ := testClient.Do(req, nil) + + err := NewJiraError(resp, errors.New("Original http error")) + msg := err.Error() + + if !strings.Contains(msg, "200 OK: Original message body: Original http error") { + t.Errorf("Expected the HTTP status: Got\n%s\n", msg) + } +} + +func TestError_Unauthorized_NilError(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + fmt.Fprint(w, `User is not authorized`) + }) + + req, _ := testClient.NewRequest("GET", "/", nil) + resp, _ := testClient.Do(req, nil) + + err := NewJiraError(resp, nil) + msg := err.Error() + if !strings.Contains(msg, "401 Unauthorized:User is not authorized") { + t.Errorf("Expected Unauthorized HTTP status: Got\n%s\n", msg) + } +} + +func TestError_BadJSON(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, `Not JSON`) + }) + + req, _ := testClient.NewRequest("GET", "/", nil) + resp, _ := testClient.Do(req, nil) + + err := NewJiraError(resp, errors.New("Original http error")) + msg := err.Error() + + if !strings.Contains(msg, "could not parse JSON") { + t.Errorf("Expected the 'could not parse JSON' error message: Got\n%s\n", msg) + } +} + +func TestError_NilOriginalMessage(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Expected an error message. Got a panic (%v)", r) + } + }() + + msgErr := &Error{ + HTTPError: nil, + ErrorMessages: []string{"Issue does not exist"}, + Errors: map[string]string{ + "issuetype": "issue type is required", + "title": "title is required", + }, + } + + _ = msgErr.Error() +} + +func TestError_NilOriginalMessageLongError(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Expected an error message. Got a panic (%v)", r) + } + }() + + msgErr := &Error{ + HTTPError: nil, + ErrorMessages: []string{"Issue does not exist"}, + Errors: map[string]string{ + "issuetype": "issue type is required", + "title": "title is required", + }, + } + + _ = msgErr.LongError() +} + +func TestError_ShortMessage(t *testing.T) { + msgErr := &Error{ + HTTPError: errors.New("Original http error"), + ErrorMessages: []string{"Issue does not exist"}, + Errors: map[string]string{ + "issuetype": "issue type is required", + "title": "title is required", + }, + } + + mapErr := &Error{ + HTTPError: errors.New("Original http error"), + ErrorMessages: nil, + Errors: map[string]string{ + "issuetype": "issue type is required", + "title": "title is required", + }, + } + + noErr := &Error{ + HTTPError: errors.New("Original http error"), + ErrorMessages: nil, + Errors: nil, + } + + err := msgErr.Error() + if err != "Issue does not exist: Original http error" { + t.Errorf("Expected short message. Got %s", err) + } + + err = mapErr.Error() + if !(strings.Contains(err, "issue type is required") || strings.Contains(err, "title is required")) { + t.Errorf("Expected short message. Got %s", err) + } + + err = noErr.Error() + if err != "Original http error" { + t.Errorf("Expected original error message. Got %s", err) + } +} + +func TestError_LongMessage(t *testing.T) { + longError := &Error{ + HTTPError: errors.New("Original http error"), + ErrorMessages: []string{"Issue does not exist."}, + Errors: map[string]string{ + "issuetype": "issue type is required", + "title": "title is required", + }, + } + + msg := longError.LongError() + if !strings.Contains(msg, "Original http error") { + t.Errorf("Expected the error message: Got\n%s\n", msg) + } + + if !strings.Contains(msg, "Issue does not exist") { + t.Errorf("Expected the error message: Got\n%s\n", msg) + } + + if !strings.Contains(msg, "title - title is required") { + t.Errorf("Expected the error map: Got\n%s\n", msg) + } +} diff --git a/onpremise/examples/addlabel/main.go b/onpremise/examples/addlabel/main.go new file mode 100644 index 0000000..2f0c30a --- /dev/null +++ b/onpremise/examples/addlabel/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" + "syscall" + + jira "github.com/andygrunwald/go-jira/onpremise" + "golang.org/x/term" +) + +func main() { + r := bufio.NewReader(os.Stdin) + + fmt.Print("Jira URL: ") + jiraURL, _ := r.ReadString('\n') + + fmt.Print("Jira Username: ") + username, _ := r.ReadString('\n') + + fmt.Print("Jira Password: ") + bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) + password := string(bytePassword) + + fmt.Print("Jira Issue ID: ") + issueId, _ := r.ReadString('\n') + issueId = strings.TrimSpace(issueId) + + fmt.Print("Label: ") + label, _ := r.ReadString('\n') + label = strings.TrimSpace(label) + + tp := jira.BasicAuthTransport{ + Username: strings.TrimSpace(username), + Password: strings.TrimSpace(password), + } + + client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + if err != nil { + fmt.Printf("\nerror: %v\n", err) + return + } + + type Labels struct { + Add string `json:"add" structs:"add"` + } + + type Update struct { + Labels []Labels `json:"labels" structs:"labels"` + } + + c := map[string]interface{}{ + "update": Update{ + Labels: []Labels{ + { + Add: label, + }, + }, + }, + } + + resp, err := client.Issue.UpdateIssue(issueId, c) + + if err != nil { + fmt.Println(err) + } + body, _ := io.ReadAll(resp.Body) + fmt.Println(string(body)) + + issue, _, _ := client.Issue.Get(issueId, nil) + + fmt.Printf("Issue: %s:%s\n", issue.Key, issue.Fields.Summary) + fmt.Printf("\tLabels: %+v\n", issue.Fields.Labels) +} diff --git a/onpremise/examples/basicauth/main.go b/onpremise/examples/basicauth/main.go new file mode 100644 index 0000000..8995269 --- /dev/null +++ b/onpremise/examples/basicauth/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + "syscall" + + "golang.org/x/term" + + jira "github.com/andygrunwald/go-jira/onpremise" +) + +func main() { + r := bufio.NewReader(os.Stdin) + + fmt.Print("Jira URL: ") + jiraURL, _ := r.ReadString('\n') + + fmt.Print("Jira Username: ") + username, _ := r.ReadString('\n') + + fmt.Print("Jira Password: ") + bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) + password := string(bytePassword) + + tp := jira.BasicAuthTransport{ + Username: strings.TrimSpace(username), + Password: strings.TrimSpace(password), + } + + client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + if err != nil { + fmt.Printf("\nerror: %v\n", err) + return + } + + u, _, err := client.User.Get("admin") + + if err != nil { + fmt.Printf("\nerror: %v\n", err) + return + } + + fmt.Printf("\nEmail: %v\nSuccess!\n", u.EmailAddress) + +} diff --git a/onpremise/examples/create/main.go b/onpremise/examples/create/main.go new file mode 100644 index 0000000..b306760 --- /dev/null +++ b/onpremise/examples/create/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + "syscall" + + jira "github.com/andygrunwald/go-jira/onpremise" + "golang.org/x/term" +) + +func main() { + r := bufio.NewReader(os.Stdin) + + fmt.Print("Jira URL: ") + jiraURL, _ := r.ReadString('\n') + + fmt.Print("Jira Username: ") + username, _ := r.ReadString('\n') + + fmt.Print("Jira Password: ") + bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) + password := string(bytePassword) + + tp := jira.BasicAuthTransport{ + Username: strings.TrimSpace(username), + Password: strings.TrimSpace(password), + } + + client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + if err != nil { + fmt.Printf("\nerror: %v\n", err) + return + } + + i := jira.Issue{ + Fields: &jira.IssueFields{ + Assignee: &jira.User{ + AccountID: "my-user-account-id", + }, + Reporter: &jira.User{ + AccountID: "your-user-account-id", + }, + Description: "Test Issue", + Type: jira.IssueType{ + Name: "Bug", + }, + Project: jira.Project{ + Key: "PROJ1", + }, + Summary: "Just a demo issue", + }, + } + + issue, _, err := client.Issue.Create(&i) + if err != nil { + panic(err) + } + + fmt.Printf("%s: %+v\n", issue.Key, issue.Self) +} diff --git a/onpremise/examples/createwithcustomfields/main.go b/onpremise/examples/createwithcustomfields/main.go new file mode 100644 index 0000000..be125c7 --- /dev/null +++ b/onpremise/examples/createwithcustomfields/main.go @@ -0,0 +1,74 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + "syscall" + + jira "github.com/andygrunwald/go-jira/onpremise" + "github.com/trivago/tgo/tcontainer" + "golang.org/x/term" +) + +func main() { + r := bufio.NewReader(os.Stdin) + + fmt.Print("Jira URL: ") + jiraURL, _ := r.ReadString('\n') + + fmt.Print("Jira Username: ") + username, _ := r.ReadString('\n') + + fmt.Print("Jira Password: ") + bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) + password := string(bytePassword) + + fmt.Print("Custom field name (i.e. customfield_10220): ") + customFieldName, _ := r.ReadString('\n') + + fmt.Print("Custom field value: ") + customFieldValue, _ := r.ReadString('\n') + + tp := jira.BasicAuthTransport{ + Username: strings.TrimSpace(username), + Password: strings.TrimSpace(password), + } + + client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + if err != nil { + fmt.Printf("\nerror: %v\n", err) + os.Exit(1) + } + + unknowns := tcontainer.NewMarshalMap() + unknowns[customFieldName] = customFieldValue + + i := jira.Issue{ + Fields: &jira.IssueFields{ + Assignee: &jira.User{ + Name: "myuser", + }, + Reporter: &jira.User{ + Name: "youruser", + }, + Description: "Test Issue", + Type: jira.IssueType{ + Name: "Bug", + }, + Project: jira.Project{ + Key: "PROJ1", + }, + Summary: "Just a demo issue", + Unknowns: unknowns, + }, + } + + issue, _, err := client.Issue.Create(&i) + if err != nil { + panic(err) + } + + fmt.Printf("%s: %v\n", issue.Key, issue.Self) +} diff --git a/onpremise/examples/do/main.go b/onpremise/examples/do/main.go new file mode 100644 index 0000000..56f0210 --- /dev/null +++ b/onpremise/examples/do/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + + jira "github.com/andygrunwald/go-jira/onpremise" +) + +func main() { + jiraClient, _ := jira.NewClient(nil, "https://jira.atlassian.com/") + req, _ := jiraClient.NewRequest("GET", "/rest/api/2/project", nil) + + projects := new([]jira.Project) + res, err := jiraClient.Do(req, projects) + if err != nil { + panic(err) + } + defer res.Body.Close() + + for _, project := range *projects { + fmt.Printf("%s: %s\n", project.Key, project.Name) + } +} diff --git a/onpremise/examples/ignorecerts/main.go b/onpremise/examples/ignorecerts/main.go new file mode 100644 index 0000000..76e42dc --- /dev/null +++ b/onpremise/examples/ignorecerts/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "crypto/tls" + "fmt" + "net/http" + + jira "github.com/andygrunwald/go-jira/onpremise" +) + +func main() { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + + jiraClient, _ := jira.NewClient(client, "https://issues.apache.org/jira/") + issue, _, _ := jiraClient.Issue.Get("MESOS-3325", nil) + + fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary) + fmt.Printf("Type: %s\n", issue.Fields.Type.Name) + fmt.Printf("Priority: %s\n", issue.Fields.Priority.Name) + +} diff --git a/onpremise/examples/jql/main.go b/onpremise/examples/jql/main.go new file mode 100644 index 0000000..1a78fe9 --- /dev/null +++ b/onpremise/examples/jql/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + + jira "github.com/andygrunwald/go-jira/onpremise" +) + +func main() { + jiraClient, _ := jira.NewClient(nil, "https://issues.apache.org/jira/") + + // Running JQL query + + 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) + if err != nil { + panic(err) + } + outputResponse(issues, resp) + + fmt.Println("") + fmt.Println("") + + // 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) + if err != nil { + panic(err) + } + 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) + } +} diff --git a/onpremise/examples/newclient/main.go b/onpremise/examples/newclient/main.go new file mode 100644 index 0000000..b63a30f --- /dev/null +++ b/onpremise/examples/newclient/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "fmt" + + jira "github.com/andygrunwald/go-jira/onpremise" +) + +func main() { + jiraClient, _ := jira.NewClient(nil, "https://issues.apache.org/jira/") + issue, _, _ := jiraClient.Issue.Get("MESOS-3325", nil) + + fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary) + fmt.Printf("Type: %s\n", issue.Fields.Type.Name) + fmt.Printf("Priority: %s\n", issue.Fields.Priority.Name) +} diff --git a/onpremise/examples/pagination/main.go b/onpremise/examples/pagination/main.go new file mode 100644 index 0000000..f60642a --- /dev/null +++ b/onpremise/examples/pagination/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + + jira "github.com/andygrunwald/go-jira/onpremise" +) + +// GetAllIssues will implement pagination of api and get all the issues. +// Jira API has limitation as to maxResults it can return at one time. +// You may have usecase where you need to get all the issues according to jql +// This is where this example comes in. +func GetAllIssues(client *jira.Client, searchString string) ([]jira.Issue, error) { + last := 0 + var issues []jira.Issue + for { + opt := &jira.SearchOptions{ + MaxResults: 1000, // Max results can go up to 1000 + StartAt: last, + } + + chunk, resp, err := client.Issue.Search(searchString, opt) + if err != nil { + return nil, err + } + + total := resp.Total + if issues == nil { + issues = make([]jira.Issue, 0, total) + } + issues = append(issues, chunk...) + last = resp.StartAt + len(chunk) + if last >= total { + return issues, nil + } + } + +} + +func main() { + jiraClient, err := jira.NewClient(nil, "https://issues.apache.org/jira/") + if err != nil { + panic(err) + } + + jql := "project = Mesos and type = Bug and Status NOT IN (Resolved)" + fmt.Printf("Usecase: Running a JQL query '%s'\n", jql) + + issues, err := GetAllIssues(jiraClient, jql) + if err != nil { + panic(err) + } + fmt.Println(issues) + +} diff --git a/onpremise/examples/renderedfields/main.go b/onpremise/examples/renderedfields/main.go new file mode 100644 index 0000000..45cdcee --- /dev/null +++ b/onpremise/examples/renderedfields/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "bufio" + "fmt" + "net/http" + "os" + "strings" + "syscall" + + "golang.org/x/term" + + jira "github.com/andygrunwald/go-jira/onpremise" +) + +func main() { + r := bufio.NewReader(os.Stdin) + + fmt.Print("Jira URL: ") + jiraURL, _ := r.ReadString('\n') + + fmt.Print("Jira Issue key: ") + key, _ := r.ReadString('\n') + key = strings.TrimSpace(key) + + fmt.Print("Jira Username: ") + username, _ := r.ReadString('\n') + + fmt.Print("Jira Password: ") + bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) + password := string(bytePassword) + + var tp *http.Client + + if strings.TrimSpace(username) == "" { + tp = nil + } else { + + ba := jira.BasicAuthTransport{ + Username: strings.TrimSpace(username), + Password: strings.TrimSpace(password), + } + tp = ba.Client() + } + + client, err := jira.NewClient(tp, strings.TrimSpace(jiraURL)) + if err != nil { + fmt.Printf("\nerror: %v\n", err) + return + } + + fmt.Printf("Targeting %s for issue %s\n", strings.TrimSpace(jiraURL), key) + + options := &jira.GetQueryOptions{Expand: "renderedFields"} + u, _, err := client.Issue.Get(key, options) + + if err != nil { + fmt.Printf("\n==> error: %v\n", err) + return + } + + fmt.Printf("RenderedFields: %+v\n", *u.RenderedFields) + + for _, c := range u.RenderedFields.Comments.Comments { + fmt.Printf(" %+v\n", c) + } +} diff --git a/onpremise/examples/searchpages/main.go b/onpremise/examples/searchpages/main.go new file mode 100644 index 0000000..8ba33ad --- /dev/null +++ b/onpremise/examples/searchpages/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "os" + "strings" + "syscall" + "time" + + jira "github.com/andygrunwald/go-jira/onpremise" + "golang.org/x/term" +) + +func main() { + r := bufio.NewReader(os.Stdin) + + fmt.Print("Jira URL: ") + jiraURL, _ := r.ReadString('\n') + + fmt.Print("Jira Username: ") + username, _ := r.ReadString('\n') + + fmt.Print("Jira Password: ") + bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) + password := string(bytePassword) + + fmt.Print("\nJira Project Key: ") // e.g. TES or WOW + jiraPK, _ := r.ReadString('\n') + + tp := jira.BasicAuthTransport{ + Username: strings.TrimSpace(username), + Password: strings.TrimSpace(password), + } + + client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + if err != nil { + log.Fatal(err) + } + + var issues []jira.Issue + + // appendFunc will append jira issues to []jira.Issue + appendFunc := func(i jira.Issue) (err error) { + issues = append(issues, i) + return err + } + + // SearchPages will page through results and pass each issue to appendFunc + // In this example, we'll search for all the issues in the target project + err = client.Issue.SearchPages(fmt.Sprintf(`project=%s`, strings.TrimSpace(jiraPK)), nil, appendFunc) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("%d issues found.\n", len(issues)) + + for _, i := range issues { + t := time.Time(i.Fields.Created) // convert go-jira.Time to time.Time for manipulation + date := t.Format("2006-01-02") + clock := t.Format("15:04") + fmt.Printf("Creation Date: %s\nCreation Time: %s\nIssue Key: %s\nIssue Summary: %s\n\n", date, clock, i.Key, i.Fields.Summary) + } + +} diff --git a/onpremise/field.go b/onpremise/field.go new file mode 100644 index 0000000..166b9c4 --- /dev/null +++ b/onpremise/field.go @@ -0,0 +1,55 @@ +package onpremise + +import "context" + +// FieldService handles fields for the Jira instance / API. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Field +type FieldService struct { + client *Client +} + +// Field represents a field of a Jira issue. +type Field struct { + ID string `json:"id,omitempty" structs:"id,omitempty"` + Key string `json:"key,omitempty" structs:"key,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + Custom bool `json:"custom,omitempty" structs:"custom,omitempty"` + Navigable bool `json:"navigable,omitempty" structs:"navigable,omitempty"` + Searchable bool `json:"searchable,omitempty" structs:"searchable,omitempty"` + ClauseNames []string `json:"clauseNames,omitempty" structs:"clauseNames,omitempty"` + Schema FieldSchema `json:"schema,omitempty" structs:"schema,omitempty"` +} + +// FieldSchema represents a schema of a Jira field. +// Documentation: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-fields/#api-rest-api-2-field-get +type FieldSchema struct { + Type string `json:"type,omitempty" structs:"type,omitempty"` + Items string `json:"items,omitempty" structs:"items,omitempty"` + Custom string `json:"custom,omitempty" structs:"custom,omitempty"` + System string `json:"system,omitempty" structs:"system,omitempty"` + CustomID int64 `json:"customId,omitempty" structs:"customId,omitempty"` +} + +// GetListWithContext gets all fields from Jira +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get +func (s *FieldService) GetListWithContext(ctx context.Context) ([]Field, *Response, error) { + apiEndpoint := "rest/api/2/field" + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + fieldList := []Field{} + resp, err := s.client.Do(req, &fieldList) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return fieldList, resp, nil +} + +// GetList wraps GetListWithContext using the background context. +func (s *FieldService) GetList() ([]Field, *Response, error) { + return s.GetListWithContext(context.Background()) +} diff --git a/onpremise/field_test.go b/onpremise/field_test.go new file mode 100644 index 0000000..763d5b6 --- /dev/null +++ b/onpremise/field_test.go @@ -0,0 +1,32 @@ +package onpremise + +import ( + "fmt" + "net/http" + "os" + "testing" +) + +func TestFieldService_GetList(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/2/field" + + raw, err := os.ReadFile("../testing/mock-data/all_fields.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, string(raw)) + }) + + fields, _, err := testClient.Field.GetList() + if fields == nil { + t.Error("Expected field list. Field list is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} diff --git a/onpremise/filter.go b/onpremise/filter.go new file mode 100644 index 0000000..07ead72 --- /dev/null +++ b/onpremise/filter.go @@ -0,0 +1,251 @@ +package onpremise + +import ( + "context" + "fmt" + + "github.com/google/go-querystring/query" +) + +// FilterService handles fields for the Jira instance / API. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Filter +type FilterService struct { + client *Client +} + +// Filter represents a Filter in Jira +type Filter struct { + Self string `json:"self"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Owner User `json:"owner"` + Jql string `json:"jql"` + ViewURL string `json:"viewUrl"` + SearchURL string `json:"searchUrl"` + Favourite bool `json:"favourite"` + FavouritedCount int `json:"favouritedCount"` + SharePermissions []interface{} `json:"sharePermissions"` + Subscriptions struct { + Size int `json:"size"` + Items []interface{} `json:"items"` + MaxResults int `json:"max-results"` + StartIndex int `json:"start-index"` + EndIndex int `json:"end-index"` + } `json:"subscriptions"` +} + +// GetMyFiltersQueryOptions specifies the optional parameters for the Get My Filters method +type GetMyFiltersQueryOptions struct { + IncludeFavourites bool `url:"includeFavourites,omitempty"` + Expand string `url:"expand,omitempty"` +} + +// FiltersList reflects a list of filters +type FiltersList struct { + MaxResults int `json:"maxResults" structs:"maxResults"` + StartAt int `json:"startAt" structs:"startAt"` + Total int `json:"total" structs:"total"` + IsLast bool `json:"isLast" structs:"isLast"` + Values []FiltersListItem `json:"values" structs:"values"` +} + +// FiltersListItem represents a Filter of FiltersList in Jira +type FiltersListItem struct { + Self string `json:"self"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Owner User `json:"owner"` + Jql string `json:"jql"` + ViewURL string `json:"viewUrl"` + SearchURL string `json:"searchUrl"` + Favourite bool `json:"favourite"` + FavouritedCount int `json:"favouritedCount"` + SharePermissions []interface{} `json:"sharePermissions"` + Subscriptions []struct { + ID int `json:"id"` + User User `json:"user"` + } `json:"subscriptions"` +} + +// FilterSearchOptions specifies the optional parameters for the Search method +// https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-search-get +type FilterSearchOptions struct { + // String used to perform a case-insensitive partial match with name. + FilterName string `url:"filterName,omitempty"` + + // User account ID used to return filters with the matching owner.accountId. This parameter cannot be used with owner. + AccountID string `url:"accountId,omitempty"` + + // Group name used to returns filters that are shared with a group that matches sharePermissions.group.groupname. + GroupName string `url:"groupname,omitempty"` + + // Project ID used to returns filters that are shared with a project that matches sharePermissions.project.id. + // Format: int64 + ProjectID int64 `url:"projectId,omitempty"` + + // Orders the results using one of these filter properties. + // - `description` Orders by filter `description`. Note that this ordering works independently of whether the expand to display the description field is in use. + // - `favourite_count` Orders by `favouritedCount`. + // - `is_favourite` Orders by `favourite`. + // - `id` Orders by filter `id`. + // - `name` Orders by filter `name`. + // - `owner` Orders by `owner.accountId`. + // + // Default: `name` + // + // Valid values: id, name, description, owner, favorite_count, is_favorite, -id, -name, -description, -owner, -favorite_count, -is_favorite + OrderBy string `url:"orderBy,omitempty"` + + // The index of the first item to return in a page of results (page offset). + // Default: 0, Format: int64 + StartAt int64 `url:"startAt,omitempty"` + + // The maximum number of items to return per page. The maximum is 100. + // Default: 50, Format: int32 + MaxResults int32 `url:"maxResults,omitempty"` + + // Use expand to include additional information about filter in the response. This parameter accepts multiple values separated by a comma: + // - description Returns the description of the filter. + // - favourite Returns an indicator of whether the user has set the filter as a favorite. + // - favouritedCount Returns a count of how many users have set this filter as a favorite. + // - jql Returns the JQL query that the filter uses. + // - owner Returns the owner of the filter. + // - searchUrl Returns a URL to perform the filter's JQL query. + // - sharePermissions Returns the share permissions defined for the filter. + // - subscriptions Returns the users that are subscribed to the filter. + // - viewUrl Returns a URL to view the filter. + Expand string `url:"expand,omitempty"` +} + +// GetListWithContext retrieves all filters from Jira +func (fs *FilterService) GetListWithContext(ctx context.Context) ([]*Filter, *Response, error) { + + options := &GetQueryOptions{} + apiEndpoint := "rest/api/2/filter" + req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + q, err := query.Values(options) + if err != nil { + return nil, nil, err + } + req.URL.RawQuery = q.Encode() + + filters := []*Filter{} + resp, err := fs.client.Do(req, &filters) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + return filters, resp, err +} + +// GetList wraps GetListWithContext using the background context. +func (fs *FilterService) GetList() ([]*Filter, *Response, error) { + return fs.GetListWithContext(context.Background()) +} + +// GetFavouriteListWithContext retrieves the user's favourited filters from Jira +func (fs *FilterService) GetFavouriteListWithContext(ctx context.Context) ([]*Filter, *Response, error) { + apiEndpoint := "rest/api/2/filter/favourite" + req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + filters := []*Filter{} + resp, err := fs.client.Do(req, &filters) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + return filters, resp, err +} + +// GetFavouriteList wraps GetFavouriteListWithContext using the background context. +func (fs *FilterService) GetFavouriteList() ([]*Filter, *Response, error) { + return fs.GetFavouriteListWithContext(context.Background()) +} + +// GetWithContext retrieves a single Filter from Jira +func (fs *FilterService) GetWithContext(ctx context.Context, filterID int) (*Filter, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/filter/%d", filterID) + req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + filter := new(Filter) + resp, err := fs.client.Do(req, filter) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return filter, resp, err +} + +// Get wraps GetWithContext using the background context. +func (fs *FilterService) Get(filterID int) (*Filter, *Response, error) { + return fs.GetWithContext(context.Background(), filterID) +} + +// GetMyFiltersWithContext retrieves the my Filters. +// +// https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-my-get +func (fs *FilterService) GetMyFiltersWithContext(ctx context.Context, opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) { + apiEndpoint := "rest/api/3/filter/my" + url, err := addOptions(apiEndpoint, opts) + if err != nil { + return nil, nil, err + } + req, err := fs.client.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, nil, err + } + + filters := []*Filter{} + resp, err := fs.client.Do(req, &filters) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + return filters, resp, nil +} + +// GetMyFilters wraps GetMyFiltersWithContext using the background context. +func (fs *FilterService) GetMyFilters(opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) { + return fs.GetMyFiltersWithContext(context.Background(), opts) +} + +// SearchWithContext will search for filter according to the search options +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-search-get +func (fs *FilterService) SearchWithContext(ctx context.Context, opt *FilterSearchOptions) (*FiltersList, *Response, error) { + apiEndpoint := "rest/api/3/filter/search" + url, err := addOptions(apiEndpoint, opt) + if err != nil { + return nil, nil, err + } + req, err := fs.client.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, nil, err + } + + filters := new(FiltersList) + resp, err := fs.client.Do(req, filters) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return filters, resp, err +} + +// Search wraps SearchWithContext using the background context. +func (fs *FilterService) Search(opt *FilterSearchOptions) (*FiltersList, *Response, error) { + return fs.SearchWithContext(context.Background(), opt) +} diff --git a/onpremise/filter_test.go b/onpremise/filter_test.go new file mode 100644 index 0000000..507753e --- /dev/null +++ b/onpremise/filter_test.go @@ -0,0 +1,126 @@ +package onpremise + +import ( + "fmt" + "net/http" + "os" + "testing" +) + +func TestFilterService_GetList(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/2/filter" + raw, err := os.ReadFile("../testing/mock-data/all_filters.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { + testMethod(t, request, "GET") + testRequestURL(t, request, testAPIEndpoint) + fmt.Fprint(writer, string(raw)) + }) + + filters, _, err := testClient.Filter.GetList() + if filters == nil { + t.Error("Expected Filters list. Filters list is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestFilterService_Get(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/2/filter/10000" + raw, err := os.ReadFile("../testing/mock-data/filter.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { + testMethod(t, request, "GET") + testRequestURL(t, request, testAPIEndpoint) + fmt.Fprint(writer, string(raw)) + }) + + filter, _, err := testClient.Filter.Get(10000) + if filter == nil { + t.Errorf("Expected Filter, got nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } + +} + +func TestFilterService_GetFavouriteList(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/2/filter/favourite" + raw, err := os.ReadFile("../testing/mock-data/favourite_filters.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { + testMethod(t, request, "GET") + testRequestURL(t, request, testAPIEndpoint) + fmt.Fprint(writer, string(raw)) + }) + + filters, _, err := testClient.Filter.GetFavouriteList() + if filters == nil { + t.Error("Expected Filters list. Filters list is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestFilterService_GetMyFilters(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/3/filter/my" + raw, err := os.ReadFile("../testing/mock-data/my_filters.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { + testMethod(t, request, "GET") + testRequestURL(t, request, testAPIEndpoint) + fmt.Fprint(writer, string(raw)) + }) + + opts := GetMyFiltersQueryOptions{} + filters, _, err := testClient.Filter.GetMyFilters(&opts) + if err != nil { + t.Errorf("Error given: %s", err) + } + if filters == nil { + t.Errorf("Expected Filters, got nil") + } +} + +func TestFilterService_Search(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/3/filter/search" + raw, err := os.ReadFile("../testing/mock-data/search_filters.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { + testMethod(t, request, "GET") + testRequestURL(t, request, testAPIEndpoint) + fmt.Fprint(writer, string(raw)) + }) + + opt := FilterSearchOptions{} + filters, _, err := testClient.Filter.Search(&opt) + if err != nil { + t.Errorf("Error given: %s", err) + } + if filters == nil { + t.Errorf("Expected Filters, got nil") + } +} diff --git a/onpremise/group.go b/onpremise/group.go new file mode 100644 index 0000000..e197922 --- /dev/null +++ b/onpremise/group.go @@ -0,0 +1,179 @@ +package onpremise + +import ( + "context" + "fmt" + "net/url" +) + +// GroupService handles Groups for the Jira instance / API. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group +type GroupService struct { + client *Client +} + +// groupMembersResult is only a small wrapper around the Group* methods +// to be able to parse the results +type groupMembersResult struct { + StartAt int `json:"startAt"` + MaxResults int `json:"maxResults"` + Total int `json:"total"` + Members []GroupMember `json:"values"` +} + +// Group represents a Jira group +type Group struct { + ID string `json:"id"` + Title string `json:"title"` + Type string `json:"type"` + Properties groupProperties `json:"properties"` + AdditionalProperties bool `json:"additionalProperties"` +} + +type groupProperties struct { + Name groupPropertiesName `json:"name"` +} + +type groupPropertiesName struct { + Type string `json:"type"` +} + +// GroupMember reflects a single member of a group +type GroupMember struct { + Self string `json:"self,omitempty"` + Name string `json:"name,omitempty"` + Key string `json:"key,omitempty"` + AccountID string `json:"accountId,omitempty"` + EmailAddress string `json:"emailAddress,omitempty"` + DisplayName string `json:"displayName,omitempty"` + Active bool `json:"active,omitempty"` + TimeZone string `json:"timeZone,omitempty"` + AccountType string `json:"accountType,omitempty"` +} + +// GroupSearchOptions specifies the optional parameters for the Get Group methods +type GroupSearchOptions struct { + StartAt int + MaxResults int + IncludeInactiveUsers bool +} + +// GetWithContext returns a paginated list of users who are members of the specified group and its subgroups. +// Users in the page are ordered by user names. +// User of this resource is required to have sysadmin or admin permissions. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup +// +// WARNING: This API only returns the first page of group members +func (s *GroupService) GetWithContext(ctx context.Context, name string) ([]GroupMember, *Response, error) { + apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + group := new(groupMembersResult) + resp, err := s.client.Do(req, group) + if err != nil { + return nil, resp, err + } + + return group.Members, resp, nil +} + +// Get wraps GetWithContext using the background context. +func (s *GroupService) Get(name string) ([]GroupMember, *Response, error) { + return s.GetWithContext(context.Background(), name) +} + +// GetWithOptionsWithContext returns a paginated list of members of the specified group and its subgroups. +// Users in the page are ordered by user names. +// User of this resource is required to have sysadmin or admin permissions. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup +func (s *GroupService) GetWithOptionsWithContext(ctx context.Context, name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { + var apiEndpoint string + if options == nil { + apiEndpoint = fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) + } else { + apiEndpoint = fmt.Sprintf( + "/rest/api/2/group/member?groupname=%s&startAt=%d&maxResults=%d&includeInactiveUsers=%t", + url.QueryEscape(name), + options.StartAt, + options.MaxResults, + options.IncludeInactiveUsers, + ) + } + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + group := new(groupMembersResult) + resp, err := s.client.Do(req, group) + if err != nil { + return nil, resp, err + } + return group.Members, resp, nil +} + +// GetWithOptions wraps GetWithOptionsWithContext using the background context. +func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { + return s.GetWithOptionsWithContext(context.Background(), name, options) +} + +// AddWithContext adds user to group +// +// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-addUserToGroup +func (s *GroupService) AddWithContext(ctx context.Context, groupname string, username string) (*Group, *Response, error) { + apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s", groupname) + var user struct { + Name string `json:"name"` + } + user.Name = username + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, &user) + if err != nil { + return nil, nil, err + } + + responseGroup := new(Group) + resp, err := s.client.Do(req, responseGroup) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return responseGroup, resp, nil +} + +// Add wraps AddWithContext using the background context. +func (s *GroupService) Add(groupname string, username string) (*Group, *Response, error) { + return s.AddWithContext(context.Background(), groupname, username) +} + +// RemoveWithContext removes user from group +// +// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-removeUserFromGroup +// Caller must close resp.Body +func (s *GroupService) RemoveWithContext(ctx context.Context, groupname string, username string) (*Response, error) { + apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s&username=%s", groupname, username) + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return resp, jerr + } + + return resp, nil +} + +// Remove wraps RemoveWithContext using the background context. +// Caller must close resp.Body +func (s *GroupService) Remove(groupname string, username string) (*Response, error) { + return s.RemoveWithContext(context.Background(), groupname, username) +} diff --git a/onpremise/group_test.go b/onpremise/group_test.go new file mode 100644 index 0000000..acdb15b --- /dev/null +++ b/onpremise/group_test.go @@ -0,0 +1,111 @@ +package onpremise + +import ( + "fmt" + "net/http" + "testing" +) + +func TestGroupService_Get(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/group/member", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/group/member?groupname=default") + fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/group/member?includeInactiveUsers=false&maxResults=50&groupname=default&startAt=0","maxResults":50,"startAt":0,"total":2,"isLast":true,"values":[{"self":"http://www.example.com/jira/rest/api/2/user?username=michael","name":"michael","key":"michael","emailAddress":"michael@example.com","displayName":"MichaelScofield","active":true,"timeZone":"Australia/Sydney"},{"self":"http://www.example.com/jira/rest/api/2/user?username=alex","name":"alex","key":"alex","emailAddress":"alex@example.com","displayName":"AlexanderMahone","active":true,"timeZone":"Australia/Sydney"}]}`) + }) + if members, _, err := testClient.Group.Get("default"); err != nil { + t.Errorf("Error given: %s", err) + } else if members == nil { + t.Error("Expected members. Group.Members is nil") + } +} + +func TestGroupService_GetPage(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/group/member", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/group/member?groupname=default") + startAt := r.URL.Query().Get("startAt") + if startAt == "0" { + fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/group/member?includeInactiveUsers=false&maxResults=2&groupname=default&startAt=0","nextPage":"`+testServer.URL+`/rest/api/2/group/member?groupname=default&includeInactiveUsers=false&maxResults=2&startAt=2","maxResults":2,"startAt":0,"total":4,"isLast":false,"values":[{"self":"http://www.example.com/jira/rest/api/2/user?username=michael","name":"michael","key":"michael","emailAddress":"michael@example.com","displayName":"MichaelScofield","active":true,"timeZone":"Australia/Sydney"},{"self":"http://www.example.com/jira/rest/api/2/user?username=alex","name":"alex","key":"alex","emailAddress":"alex@example.com","displayName":"AlexanderMahone","active":true,"timeZone":"Australia/Sydney"}]}`) + } else if startAt == "2" { + fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/group/member?includeInactiveUsers=false&maxResults=2&groupname=default&startAt=2","maxResults":2,"startAt":2,"total":4,"isLast":true,"values":[{"self":"http://www.example.com/jira/rest/api/2/user?username=michael","name":"michael","key":"michael","emailAddress":"michael@example.com","displayName":"MichaelScofield","active":true,"timeZone":"Australia/Sydney"},{"self":"http://www.example.com/jira/rest/api/2/user?username=alex","name":"alex","key":"alex","emailAddress":"alex@example.com","displayName":"AlexanderMahone","active":true,"timeZone":"Australia/Sydney"}]}`) + } else { + t.Errorf("startAt %s", startAt) + } + }) + if page, resp, err := testClient.Group.GetWithOptions("default", &GroupSearchOptions{ + StartAt: 0, + MaxResults: 2, + IncludeInactiveUsers: false, + }); err != nil { + t.Errorf("Error given: %s %s", err, testServer.URL) + } else if page == nil || len(page) != 2 { + t.Error("Expected members. Group.Members is not 2 or is nil") + } else { + if resp.StartAt != 0 { + t.Errorf("Expect Result StartAt to be 0, but is %d", resp.StartAt) + } + if resp.MaxResults != 2 { + t.Errorf("Expect Result MaxResults to be 2, but is %d", resp.MaxResults) + } + if resp.Total != 4 { + t.Errorf("Expect Result Total to be 4, but is %d", resp.Total) + } + if page, resp, err := testClient.Group.GetWithOptions("default", &GroupSearchOptions{ + StartAt: 2, + MaxResults: 2, + IncludeInactiveUsers: false, + }); err != nil { + t.Errorf("Error give: %s %s", err, testServer.URL) + } else if page == nil || len(page) != 2 { + t.Error("Expected members. Group.Members is not 2 or is nil") + } else { + if resp.StartAt != 2 { + t.Errorf("Expect Result StartAt to be 2, but is %d", resp.StartAt) + } + if resp.MaxResults != 2 { + t.Errorf("Expect Result MaxResults to be 2, but is %d", resp.MaxResults) + } + if resp.Total != 4 { + t.Errorf("Expect Result Total to be 4, but is %d", resp.Total) + } + } + } +} + +func TestGroupService_Add(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/group/user", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/group/user?groupname=default") + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"name":"default","self":"http://www.example.com/jira/rest/api/2/group?groupname=default","users":{"size":1,"items":[],"max-results":50,"start-index":0,"end-index":0},"expand":"users"}`) + }) + + if group, _, err := testClient.Group.Add("default", "theodore"); err != nil { + t.Errorf("Error given: %s", err) + } else if group == nil { + t.Error("Expected group. Group is nil") + } +} + +func TestGroupService_Remove(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/group/user", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, "/rest/api/2/group/user?groupname=default") + + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"name":"default","self":"http://www.example.com/jira/rest/api/2/group?groupname=default","users":{"size":1,"items":[],"max-results":50,"start-index":0,"end-index":0},"expand":"users"}`) + }) + + if _, err := testClient.Group.Remove("default", "theodore"); err != nil { + t.Errorf("Error given: %s", err) + } +} diff --git a/onpremise/issue.go b/onpremise/issue.go new file mode 100644 index 0000000..4f82017 --- /dev/null +++ b/onpremise/issue.go @@ -0,0 +1,1598 @@ +package onpremise + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "reflect" + "strconv" + "strings" + "time" + + "github.com/fatih/structs" + "github.com/google/go-querystring/query" + "github.com/trivago/tgo/tcontainer" +) + +const ( + // AssigneeAutomatic represents the value of the "Assignee: Automatic" of Jira + AssigneeAutomatic = "-1" +) + +// IssueService handles Issues for the Jira instance / API. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue +type IssueService struct { + client *Client +} + +// UpdateQueryOptions specifies the optional parameters to the Edit issue +type UpdateQueryOptions struct { + NotifyUsers bool `url:"notifyUsers,omitempty"` + OverrideScreenSecurity bool `url:"overrideScreenSecurity,omitempty"` + OverrideEditableFlag bool `url:"overrideEditableFlag,omitempty"` +} + +// Issue represents a Jira issue. +type Issue struct { + Expand string `json:"expand,omitempty" structs:"expand,omitempty"` + ID string `json:"id,omitempty" structs:"id,omitempty"` + Self string `json:"self,omitempty" structs:"self,omitempty"` + Key string `json:"key,omitempty" structs:"key,omitempty"` + Fields *IssueFields `json:"fields,omitempty" structs:"fields,omitempty"` + RenderedFields *IssueRenderedFields `json:"renderedFields,omitempty" structs:"renderedFields,omitempty"` + Changelog *Changelog `json:"changelog,omitempty" structs:"changelog,omitempty"` + Transitions []Transition `json:"transitions,omitempty" structs:"transitions,omitempty"` + Names map[string]string `json:"names,omitempty" structs:"names,omitempty"` +} + +// ChangelogItems reflects one single changelog item of a history item +type ChangelogItems struct { + Field string `json:"field" structs:"field"` + FieldType string `json:"fieldtype" structs:"fieldtype"` + From interface{} `json:"from" structs:"from"` + FromString string `json:"fromString" structs:"fromString"` + To interface{} `json:"to" structs:"to"` + ToString string `json:"toString" structs:"toString"` +} + +// ChangelogHistory reflects one single changelog history entry +type ChangelogHistory struct { + Id string `json:"id" structs:"id"` + Author User `json:"author" structs:"author"` + Created string `json:"created" structs:"created"` + Items []ChangelogItems `json:"items" structs:"items"` +} + +// Changelog reflects the change log of an issue +type Changelog struct { + Histories []ChangelogHistory `json:"histories,omitempty"` +} + +// Attachment represents a Jira attachment +type Attachment struct { + Self string `json:"self,omitempty" structs:"self,omitempty"` + ID string `json:"id,omitempty" structs:"id,omitempty"` + Filename string `json:"filename,omitempty" structs:"filename,omitempty"` + Author *User `json:"author,omitempty" structs:"author,omitempty"` + Created string `json:"created,omitempty" structs:"created,omitempty"` + Size int `json:"size,omitempty" structs:"size,omitempty"` + MimeType string `json:"mimeType,omitempty" structs:"mimeType,omitempty"` + Content string `json:"content,omitempty" structs:"content,omitempty"` + Thumbnail string `json:"thumbnail,omitempty" structs:"thumbnail,omitempty"` +} + +// Epic represents the epic to which an issue is associated +// Not that this struct does not process the returned "color" value +type Epic struct { + ID int `json:"id" structs:"id"` + Key string `json:"key" structs:"key"` + Self string `json:"self" structs:"self"` + Name string `json:"name" structs:"name"` + Summary string `json:"summary" structs:"summary"` + Done bool `json:"done" structs:"done"` +} + +// IssueFields represents single fields of a Jira issue. +// Every Jira issue has several fields attached. +type IssueFields struct { + // TODO Missing fields + // * "workratio": -1, + // * "lastViewed": null, + // * "environment": null, + Expand string `json:"expand,omitempty" structs:"expand,omitempty"` + Type IssueType `json:"issuetype,omitempty" structs:"issuetype,omitempty"` + Project Project `json:"project,omitempty" structs:"project,omitempty"` + Environment string `json:"environment,omitempty" structs:"environment,omitempty"` + Resolution *Resolution `json:"resolution,omitempty" structs:"resolution,omitempty"` + Priority *Priority `json:"priority,omitempty" structs:"priority,omitempty"` + Resolutiondate Time `json:"resolutiondate,omitempty" structs:"resolutiondate,omitempty"` + Created Time `json:"created,omitempty" structs:"created,omitempty"` + Duedate Date `json:"duedate,omitempty" structs:"duedate,omitempty"` + Watches *Watches `json:"watches,omitempty" structs:"watches,omitempty"` + Assignee *User `json:"assignee,omitempty" structs:"assignee,omitempty"` + Updated Time `json:"updated,omitempty" structs:"updated,omitempty"` + Description string `json:"description,omitempty" structs:"description,omitempty"` + Summary string `json:"summary,omitempty" structs:"summary,omitempty"` + Creator *User `json:"Creator,omitempty" structs:"Creator,omitempty"` + Reporter *User `json:"reporter,omitempty" structs:"reporter,omitempty"` + Components []*Component `json:"components,omitempty" structs:"components,omitempty"` + Status *Status `json:"status,omitempty" structs:"status,omitempty"` + Progress *Progress `json:"progress,omitempty" structs:"progress,omitempty"` + AggregateProgress *Progress `json:"aggregateprogress,omitempty" structs:"aggregateprogress,omitempty"` + TimeTracking *TimeTracking `json:"timetracking,omitempty" structs:"timetracking,omitempty"` + TimeSpent int `json:"timespent,omitempty" structs:"timespent,omitempty"` + TimeEstimate int `json:"timeestimate,omitempty" structs:"timeestimate,omitempty"` + TimeOriginalEstimate int `json:"timeoriginalestimate,omitempty" structs:"timeoriginalestimate,omitempty"` + Worklog *Worklog `json:"worklog,omitempty" structs:"worklog,omitempty"` + IssueLinks []*IssueLink `json:"issuelinks,omitempty" structs:"issuelinks,omitempty"` + Comments *Comments `json:"comment,omitempty" structs:"comment,omitempty"` + FixVersions []*FixVersion `json:"fixVersions,omitempty" structs:"fixVersions,omitempty"` + AffectsVersions []*AffectsVersion `json:"versions,omitempty" structs:"versions,omitempty"` + Labels []string `json:"labels,omitempty" structs:"labels,omitempty"` + Subtasks []*Subtasks `json:"subtasks,omitempty" structs:"subtasks,omitempty"` + Attachments []*Attachment `json:"attachment,omitempty" structs:"attachment,omitempty"` + Epic *Epic `json:"epic,omitempty" structs:"epic,omitempty"` + Sprint *Sprint `json:"sprint,omitempty" structs:"sprint,omitempty"` + Parent *Parent `json:"parent,omitempty" structs:"parent,omitempty"` + AggregateTimeOriginalEstimate int `json:"aggregatetimeoriginalestimate,omitempty" structs:"aggregatetimeoriginalestimate,omitempty"` + AggregateTimeSpent int `json:"aggregatetimespent,omitempty" structs:"aggregatetimespent,omitempty"` + AggregateTimeEstimate int `json:"aggregatetimeestimate,omitempty" structs:"aggregatetimeestimate,omitempty"` + Unknowns tcontainer.MarshalMap +} + +// MarshalJSON is a custom JSON marshal function for the IssueFields structs. +// It handles Jira custom fields and maps those from / to "Unknowns" key. +func (i *IssueFields) MarshalJSON() ([]byte, error) { + m := structs.Map(i) + unknowns, okay := m["Unknowns"] + if okay { + // if unknowns present, shift all key value from unknown to a level up + for key, value := range unknowns.(tcontainer.MarshalMap) { + m[key] = value + } + delete(m, "Unknowns") + } + return json.Marshal(m) +} + +// UnmarshalJSON is a custom JSON marshal function for the IssueFields structs. +// It handles Jira custom fields and maps those from / to "Unknowns" key. +func (i *IssueFields) UnmarshalJSON(data []byte) error { + + // Do the normal unmarshalling first + // Details for this way: http://choly.ca/post/go-json-marshalling/ + type Alias IssueFields + aux := &struct { + *Alias + }{ + Alias: (*Alias)(i), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + totalMap := tcontainer.NewMarshalMap() + err := json.Unmarshal(data, &totalMap) + if err != nil { + return err + } + + t := reflect.TypeOf(*i) + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + tagDetail := field.Tag.Get("json") + if tagDetail == "" { + // ignore if there are no tags + continue + } + options := strings.Split(tagDetail, ",") + + if len(options) == 0 { + return fmt.Errorf("no tags options found for %s", field.Name) + } + // the first one is the json tag + key := options[0] + if _, okay := totalMap.Value(key); okay { + delete(totalMap, key) + } + + } + i = (*IssueFields)(aux.Alias) + // all the tags found in the struct were removed. Whatever is left are unknowns to struct + i.Unknowns = totalMap + return nil + +} + +// IssueRenderedFields represents rendered fields of a Jira issue. +// Not all IssueFields are rendered. +type IssueRenderedFields struct { + // TODO Missing fields + // * "aggregatetimespent": null, + // * "workratio": -1, + // * "lastViewed": null, + // * "aggregatetimeoriginalestimate": null, + // * "aggregatetimeestimate": null, + // * "environment": null, + Resolutiondate string `json:"resolutiondate,omitempty" structs:"resolutiondate,omitempty"` + Created string `json:"created,omitempty" structs:"created,omitempty"` + Duedate string `json:"duedate,omitempty" structs:"duedate,omitempty"` + Updated string `json:"updated,omitempty" structs:"updated,omitempty"` + Comments *Comments `json:"comment,omitempty" structs:"comment,omitempty"` + Description string `json:"description,omitempty" structs:"description,omitempty"` +} + +// IssueType represents a type of a Jira issue. +// Typical types are "Request", "Bug", "Story", ... +type IssueType struct { + Self string `json:"self,omitempty" structs:"self,omitempty"` + ID string `json:"id,omitempty" structs:"id,omitempty"` + Description string `json:"description,omitempty" structs:"description,omitempty"` + IconURL string `json:"iconUrl,omitempty" structs:"iconUrl,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + Subtask bool `json:"subtask,omitempty" structs:"subtask,omitempty"` + AvatarID int `json:"avatarId,omitempty" structs:"avatarId,omitempty"` +} + +// Watches represents a type of how many and which user are "observing" a Jira issue to track the status / updates. +type Watches struct { + Self string `json:"self,omitempty" structs:"self,omitempty"` + WatchCount int `json:"watchCount,omitempty" structs:"watchCount,omitempty"` + IsWatching bool `json:"isWatching,omitempty" structs:"isWatching,omitempty"` + Watchers []*Watcher `json:"watchers,omitempty" structs:"watchers,omitempty"` +} + +// Watcher represents a simplified user that "observes" the issue +type Watcher struct { + Self string `json:"self,omitempty" structs:"self,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + AccountID string `json:"accountId,omitempty" structs:"accountId,omitempty"` + DisplayName string `json:"displayName,omitempty" structs:"displayName,omitempty"` + Active bool `json:"active,omitempty" structs:"active,omitempty"` +} + +// AvatarUrls represents different dimensions of avatars / images +type AvatarUrls struct { + Four8X48 string `json:"48x48,omitempty" structs:"48x48,omitempty"` + Two4X24 string `json:"24x24,omitempty" structs:"24x24,omitempty"` + One6X16 string `json:"16x16,omitempty" structs:"16x16,omitempty"` + Three2X32 string `json:"32x32,omitempty" structs:"32x32,omitempty"` +} + +// Component represents a "component" of a Jira issue. +// Components can be user defined in every Jira instance. +type Component struct { + Self string `json:"self,omitempty" structs:"self,omitempty"` + ID string `json:"id,omitempty" structs:"id,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + Description string `json:"description,omitempty" structs:"description,omitempty"` +} + +// Progress represents the progress of a Jira issue. +type Progress struct { + Progress int `json:"progress" structs:"progress"` + Total int `json:"total" structs:"total"` + Percent int `json:"percent" structs:"percent"` +} + +// Parent represents the parent of a Jira issue, to be used with subtask issue types. +type Parent struct { + ID string `json:"id,omitempty" structs:"id,omitempty"` + Key string `json:"key,omitempty" structs:"key,omitempty"` +} + +// Time represents the Time definition of Jira as a time.Time of go +type Time time.Time + +func (t Time) Equal(u Time) bool { + return time.Time(t).Equal(time.Time(u)) +} + +// Date represents the Date definition of Jira as a time.Time of go +type Date time.Time + +// Wrapper struct for search result +type transitionResult struct { + Transitions []Transition `json:"transitions" structs:"transitions"` +} + +// Transition represents an issue transition in Jira +type Transition struct { + ID string `json:"id" structs:"id"` + Name string `json:"name" structs:"name"` + To Status `json:"to" structs:"status"` + Fields map[string]TransitionField `json:"fields" structs:"fields"` +} + +// TransitionField represents the value of one Transition +type TransitionField struct { + Required bool `json:"required" structs:"required"` +} + +// CreateTransitionPayload is used for creating new issue transitions +type CreateTransitionPayload struct { + Update TransitionPayloadUpdate `json:"update,omitempty" structs:"update,omitempty"` + Transition TransitionPayload `json:"transition" structs:"transition"` + Fields TransitionPayloadFields `json:"fields" structs:"fields"` +} + +// TransitionPayloadUpdate represents the updates of Transition calls like DoTransition +type TransitionPayloadUpdate struct { + Comment []TransitionPayloadComment `json:"comment,omitempty" structs:"comment,omitempty"` +} + +// TransitionPayloadComment represents comment in Transition payload +type TransitionPayloadComment struct { + Add TransitionPayloadCommentBody `json:"add,omitempty" structs:"add,omitempty"` +} + +// TransitionPayloadCommentBody represents body of comment in payload +type TransitionPayloadCommentBody struct { + Body string `json:"body,omitempty"` +} + +// TransitionPayload represents the request payload of Transition calls like DoTransition +type TransitionPayload struct { + ID string `json:"id" structs:"id"` +} + +// TransitionPayloadFields represents the fields that can be set when executing a transition +type TransitionPayloadFields struct { + Resolution *Resolution `json:"resolution,omitempty" structs:"resolution,omitempty"` +} + +// Option represents an option value in a SelectList or MultiSelect +// custom issue field +type Option struct { + Value string `json:"value" structs:"value"` +} + +// UnmarshalJSON will transform the Jira time into a time.Time +// during the transformation of the Jira JSON response +func (t *Time) UnmarshalJSON(b []byte) error { + // Ignore null, like in the main JSON package. + if string(b) == "null" { + return nil + } + ti, err := time.Parse("\"2006-01-02T15:04:05.999-0700\"", string(b)) + if err != nil { + return err + } + *t = Time(ti) + return nil +} + +// MarshalJSON will transform the time.Time into a Jira time +// during the creation of a Jira request +func (t Time) MarshalJSON() ([]byte, error) { + return []byte(time.Time(t).Format("\"2006-01-02T15:04:05.000-0700\"")), nil +} + +// UnmarshalJSON will transform the Jira date into a time.Time +// during the transformation of the Jira JSON response +func (t *Date) UnmarshalJSON(b []byte) error { + // Ignore null, like in the main JSON package. + if string(b) == "null" { + return nil + } + ti, err := time.Parse("\"2006-01-02\"", string(b)) + if err != nil { + return err + } + *t = Date(ti) + return nil +} + +// MarshalJSON will transform the Date object into a short +// date string as Jira expects during the creation of a +// Jira request +func (t Date) MarshalJSON() ([]byte, error) { + time := time.Time(t) + return []byte(time.Format("\"2006-01-02\"")), nil +} + +// Worklog represents the work log of a Jira issue. +// One Worklog contains zero or n WorklogRecords +// Jira Wiki: https://confluence.atlassian.com/jira/logging-work-on-an-issue-185729605.html +type Worklog struct { + StartAt int `json:"startAt" structs:"startAt"` + MaxResults int `json:"maxResults" structs:"maxResults"` + Total int `json:"total" structs:"total"` + Worklogs []WorklogRecord `json:"worklogs" structs:"worklogs"` +} + +// WorklogRecord represents one entry of a Worklog +type WorklogRecord struct { + Self string `json:"self,omitempty" structs:"self,omitempty"` + Author *User `json:"author,omitempty" structs:"author,omitempty"` + UpdateAuthor *User `json:"updateAuthor,omitempty" structs:"updateAuthor,omitempty"` + Comment string `json:"comment,omitempty" structs:"comment,omitempty"` + Created *Time `json:"created,omitempty" structs:"created,omitempty"` + Updated *Time `json:"updated,omitempty" structs:"updated,omitempty"` + Started *Time `json:"started,omitempty" structs:"started,omitempty"` + TimeSpent string `json:"timeSpent,omitempty" structs:"timeSpent,omitempty"` + TimeSpentSeconds int `json:"timeSpentSeconds,omitempty" structs:"timeSpentSeconds,omitempty"` + ID string `json:"id,omitempty" structs:"id,omitempty"` + IssueID string `json:"issueId,omitempty" structs:"issueId,omitempty"` + Properties []EntityProperty `json:"properties,omitempty"` +} + +type EntityProperty struct { + Key string `json:"key"` + Value interface{} `json:"value"` +} + +// TimeTracking represents the timetracking fields of a Jira issue. +type TimeTracking struct { + OriginalEstimate string `json:"originalEstimate,omitempty" structs:"originalEstimate,omitempty"` + RemainingEstimate string `json:"remainingEstimate,omitempty" structs:"remainingEstimate,omitempty"` + TimeSpent string `json:"timeSpent,omitempty" structs:"timeSpent,omitempty"` + OriginalEstimateSeconds int `json:"originalEstimateSeconds,omitempty" structs:"originalEstimateSeconds,omitempty"` + RemainingEstimateSeconds int `json:"remainingEstimateSeconds,omitempty" structs:"remainingEstimateSeconds,omitempty"` + TimeSpentSeconds int `json:"timeSpentSeconds,omitempty" structs:"timeSpentSeconds,omitempty"` +} + +// Subtasks represents all issues of a parent issue. +type Subtasks struct { + ID string `json:"id" structs:"id"` + Key string `json:"key" structs:"key"` + Self string `json:"self" structs:"self"` + Fields IssueFields `json:"fields" structs:"fields"` +} + +// IssueLink represents a link between two issues in Jira. +type IssueLink struct { + ID string `json:"id,omitempty" structs:"id,omitempty"` + Self string `json:"self,omitempty" structs:"self,omitempty"` + Type IssueLinkType `json:"type" structs:"type"` + OutwardIssue *Issue `json:"outwardIssue" structs:"outwardIssue"` + InwardIssue *Issue `json:"inwardIssue" structs:"inwardIssue"` + Comment *Comment `json:"comment,omitempty" structs:"comment,omitempty"` +} + +// IssueLinkType represents a type of a link between to issues in Jira. +// Typical issue link types are "Related to", "Duplicate", "Is blocked by", etc. +type IssueLinkType struct { + ID string `json:"id,omitempty" structs:"id,omitempty"` + Self string `json:"self,omitempty" structs:"self,omitempty"` + Name string `json:"name" structs:"name"` + Inward string `json:"inward" structs:"inward"` + Outward string `json:"outward" structs:"outward"` +} + +// Comments represents a list of Comment. +type Comments struct { + Comments []*Comment `json:"comments,omitempty" structs:"comments,omitempty"` +} + +// Comment represents a comment by a person to an issue in Jira. +type Comment struct { + ID string `json:"id,omitempty" structs:"id,omitempty"` + Self string `json:"self,omitempty" structs:"self,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + Author User `json:"author,omitempty" structs:"author,omitempty"` + Body string `json:"body,omitempty" structs:"body,omitempty"` + UpdateAuthor User `json:"updateAuthor,omitempty" structs:"updateAuthor,omitempty"` + Updated string `json:"updated,omitempty" structs:"updated,omitempty"` + Created string `json:"created,omitempty" structs:"created,omitempty"` + Visibility CommentVisibility `json:"visibility,omitempty" structs:"visibility,omitempty"` +} + +// FixVersion represents a software release in which an issue is fixed. +type FixVersion struct { + Self string `json:"self,omitempty" structs:"self,omitempty"` + ID string `json:"id,omitempty" structs:"id,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + Description string `json:"description,omitempty" structs:"description,omitempty"` + Archived *bool `json:"archived,omitempty" structs:"archived,omitempty"` + Released *bool `json:"released,omitempty" structs:"released,omitempty"` + ReleaseDate string `json:"releaseDate,omitempty" structs:"releaseDate,omitempty"` + UserReleaseDate string `json:"userReleaseDate,omitempty" structs:"userReleaseDate,omitempty"` + ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"` // Unlike other IDs, this is returned as a number + StartDate string `json:"startDate,omitempty" structs:"startDate,omitempty"` +} + +// AffectsVersion represents a software release which is affected by an issue. +type AffectsVersion Version + +// CommentVisibility represents he visibility of a comment. +// E.g. Type could be "role" and Value "Administrators" +type CommentVisibility struct { + Type string `json:"type,omitempty" structs:"type,omitempty"` + Value string `json:"value,omitempty" structs:"value,omitempty"` +} + +// SearchOptions specifies the optional parameters to various List methods that +// support pagination. +// Pagination is used for the Jira REST APIs to conserve server resources and limit +// response size for resources that return potentially large collection of items. +// A request to a pages API will result in a values array wrapped in a JSON object with some paging metadata +// Default Pagination options +type SearchOptions struct { + // StartAt: The starting index of the returned projects. Base index: 0. + StartAt int `url:"startAt,omitempty"` + // MaxResults: The maximum number of projects to return per page. Default: 50. + MaxResults int `url:"maxResults,omitempty"` + // Expand: Expand specific sections in the returned issues + Expand string `url:"expand,omitempty"` + Fields []string + // ValidateQuery: The validateQuery param offers control over whether to validate and how strictly to treat the validation. Default: strict. + ValidateQuery string `url:"validateQuery,omitempty"` +} + +// 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"` +} + +// 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. + Fields string `url:"fields,omitempty"` + Expand string `url:"expand,omitempty"` + // Properties is the list of properties to return for the issue. By default no properties are returned. + Properties string `url:"properties,omitempty"` + // FieldsByKeys if true then fields in issues will be referenced by keys instead of ids + FieldsByKeys bool `url:"fieldsByKeys,omitempty"` + UpdateHistory bool `url:"updateHistory,omitempty"` + ProjectKeys string `url:"projectKeys,omitempty"` +} + +// GetWorklogsQueryOptions specifies the optional parameters for the Get Worklogs method +type GetWorklogsQueryOptions struct { + StartAt int64 `url:"startAt,omitempty"` + MaxResults int32 `url:"maxResults,omitempty"` + StartedAfter int64 `url:"startedAfter,omitempty"` + Expand string `url:"expand,omitempty"` +} + +type AddWorklogQueryOptions struct { + NotifyUsers bool `url:"notifyUsers,omitempty"` + AdjustEstimate string `url:"adjustEstimate,omitempty"` + NewEstimate string `url:"newEstimate,omitempty"` + ReduceBy string `url:"reduceBy,omitempty"` + Expand string `url:"expand,omitempty"` + OverrideEditableFlag bool `url:"overrideEditableFlag,omitempty"` +} + +// CustomFields represents custom fields of Jira +// This can heavily differ between Jira instances +type CustomFields map[string]string + +// RemoteLink represents remote links which linked to issues +type RemoteLink struct { + ID int `json:"id,omitempty" structs:"id,omitempty"` + Self string `json:"self,omitempty" structs:"self,omitempty"` + GlobalID string `json:"globalId,omitempty" structs:"globalId,omitempty"` + Application *RemoteLinkApplication `json:"application,omitempty" structs:"application,omitempty"` + Relationship string `json:"relationship,omitempty" structs:"relationship,omitempty"` + Object *RemoteLinkObject `json:"object,omitempty" structs:"object,omitempty"` +} + +// RemoteLinkApplication represents remote links application +type RemoteLinkApplication struct { + Type string `json:"type,omitempty" structs:"type,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` +} + +// RemoteLinkObject represents remote link object itself +type RemoteLinkObject struct { + URL string `json:"url,omitempty" structs:"url,omitempty"` + Title string `json:"title,omitempty" structs:"title,omitempty"` + Summary string `json:"summary,omitempty" structs:"summary,omitempty"` + Icon *RemoteLinkIcon `json:"icon,omitempty" structs:"icon,omitempty"` + Status *RemoteLinkStatus `json:"status,omitempty" structs:"status,omitempty"` +} + +// RemoteLinkIcon represents icon displayed next to link +type RemoteLinkIcon struct { + Url16x16 string `json:"url16x16,omitempty" structs:"url16x16,omitempty"` + Title string `json:"title,omitempty" structs:"title,omitempty"` + Link string `json:"link,omitempty" structs:"link,omitempty"` +} + +// RemoteLinkStatus if the link is a resolvable object (issue, epic) - the structure represent its status +type RemoteLinkStatus struct { + Resolved bool `json:"resolved,omitempty" structs:"resolved,omitempty"` + Icon *RemoteLinkIcon `json:"icon,omitempty" structs:"icon,omitempty"` +} + +// GetWithContext returns a full representation of the issue for the given issue key. +// Jira will attempt to identify the issue by the issueIdOrKey path parameter. +// This can be an issue id, or an issue key. +// If the issue cannot be found via an exact match, Jira will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved. +// +// # The given options will be appended to the query string +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getIssue +func (s *IssueService) GetWithContext(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + if options != nil { + q, err := query.Values(options) + if err != nil { + return nil, nil, err + } + req.URL.RawQuery = q.Encode() + } + + issue := new(Issue) + resp, err := s.client.Do(req, issue) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return issue, resp, nil +} + +// Get wraps GetWithContext using the background context. +func (s *IssueService) Get(issueID string, options *GetQueryOptions) (*Issue, *Response, error) { + return s.GetWithContext(context.Background(), issueID, options) +} + +// DownloadAttachmentWithContext returns a Response of an attachment for a given attachmentID. +// The attachment is in the Response.Body of the response. +// This is an io.ReadCloser. +// Caller must close resp.Body. +func (s *IssueService) DownloadAttachmentWithContext(ctx context.Context, attachmentID string) (*Response, error) { + apiEndpoint := fmt.Sprintf("secure/attachment/%s/", attachmentID) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return resp, jerr + } + + return resp, nil +} + +// DownloadAttachment wraps DownloadAttachmentWithContext using the background context. +// Caller must close resp.Body +func (s *IssueService) DownloadAttachment(attachmentID string) (*Response, error) { + return s.DownloadAttachmentWithContext(context.Background(), attachmentID) +} + +// PostAttachmentWithContext uploads r (io.Reader) as an attachment to a given issueID +func (s *IssueService) PostAttachmentWithContext(ctx context.Context, issueID string, r io.Reader, attachmentName string) (*[]Attachment, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/attachments", issueID) + + b := new(bytes.Buffer) + writer := multipart.NewWriter(b) + + fw, err := writer.CreateFormFile("file", attachmentName) + if err != nil { + return nil, nil, err + } + + if r != nil { + // Copy the file + if _, err = io.Copy(fw, r); err != nil { + return nil, nil, err + } + } + writer.Close() + + req, err := s.client.NewMultiPartRequestWithContext(ctx, "POST", apiEndpoint, b) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Content-Type", writer.FormDataContentType()) + + // PostAttachment response returns a JSON array (as multiple attachments can be posted) + attachment := new([]Attachment) + resp, err := s.client.Do(req, attachment) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return attachment, resp, nil +} + +// PostAttachment wraps PostAttachmentWithContext using the background context. +func (s *IssueService) PostAttachment(issueID string, r io.Reader, attachmentName string) (*[]Attachment, *Response, error) { + return s.PostAttachmentWithContext(context.Background(), issueID, r, attachmentName) +} + +// DeleteAttachmentWithContext deletes an attachment of a given attachmentID +// Caller must close resp.Body +func (s *IssueService) DeleteAttachmentWithContext(ctx context.Context, attachmentID string) (*Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/attachment/%s", attachmentID) + + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return resp, jerr + } + + return resp, nil +} + +// DeleteAttachment wraps DeleteAttachmentWithContext using the background context. +// Caller must close resp.Body +func (s *IssueService) DeleteAttachment(attachmentID string) (*Response, error) { + return s.DeleteAttachmentWithContext(context.Background(), attachmentID) +} + +// DeleteLinkWithContext deletes a link of a given linkID +// Caller must close resp.Body +func (s *IssueService) DeleteLinkWithContext(ctx context.Context, linkID string) (*Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issueLink/%s", linkID) + + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return resp, jerr + } + + return resp, nil +} + +// DeleteLink wraps DeleteLinkWithContext using the background context. +// Caller must close resp.Body +func (s *IssueService) DeleteLink(linkID string) (*Response, error) { + return s.DeleteLinkWithContext(context.Background(), linkID) +} + +// GetWorklogsWithContext gets all the worklogs for an issue. +// This method is especially important if you need to read all the worklogs, not just the first page. +// +// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/worklog-getIssueWorklog +func (s *IssueService) GetWorklogsWithContext(ctx context.Context, issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) + + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + for _, option := range options { + err = option(req) + if err != nil { + return nil, nil, err + } + } + + v := new(Worklog) + resp, err := s.client.Do(req, v) + return v, resp, err +} + +// GetWorklogs wraps GetWorklogsWithContext using the background context. +func (s *IssueService) GetWorklogs(issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) { + return s.GetWorklogsWithContext(context.Background(), issueID, options...) +} + +// Applies query options to http request. +// This helper is meant to be used with all "QueryOptions" structs. +func WithQueryOptions(options interface{}) func(*http.Request) error { + q, err := query.Values(options) + if err != nil { + return func(*http.Request) error { + return err + } + } + + return func(r *http.Request) error { + r.URL.RawQuery = q.Encode() + return nil + } +} + +// CreateWithContext creates an issue or a sub-task from a JSON representation. +// Creating a sub-task is similar to creating a regular issue, with two important differences: +// The issueType field must correspond to a sub-task issue type and you must provide a parent field in the issue create request containing the id or key of the parent issue. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-createIssues +func (s *IssueService) CreateWithContext(ctx context.Context, issue *Issue) (*Issue, *Response, error) { + apiEndpoint := "rest/api/2/issue" + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, issue) + if err != nil { + return nil, nil, err + } + resp, err := s.client.Do(req, nil) + if err != nil { + // incase of error return the resp for further inspection + return nil, resp, err + } + + responseIssue := new(Issue) + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, resp, fmt.Errorf("could not read the returned data") + } + err = json.Unmarshal(data, responseIssue) + if err != nil { + return nil, resp, fmt.Errorf("could not unmarshall the data into struct") + } + return responseIssue, resp, nil +} + +// Create wraps CreateWithContext using the background context. +func (s *IssueService) Create(issue *Issue) (*Issue, *Response, error) { + return s.CreateWithContext(context.Background(), issue) +} + +// UpdateWithOptionsWithContext updates an issue from a JSON representation, +// while also specifying query params. The issue is found by key. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-editIssue +// Caller must close resp.Body +func (s *IssueService) UpdateWithOptionsWithContext(ctx context.Context, issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", issue.Key) + url, err := addOptions(apiEndpoint, opts) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequestWithContext(ctx, "PUT", url, issue) + if err != nil { + return nil, nil, err + } + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + // This is just to follow the rest of the API's convention of returning an issue. + // Returning the same pointer here is pointless, so we return a copy instead. + ret := *issue + return &ret, resp, nil +} + +// UpdateWithOptions wraps UpdateWithOptionsWithContext using the background context. +// Caller must close resp.Body +func (s *IssueService) UpdateWithOptions(issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) { + return s.UpdateWithOptionsWithContext(context.Background(), issue, opts) +} + +// UpdateWithContext updates an issue from a JSON representation. The issue is found by key. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-editIssue +func (s *IssueService) UpdateWithContext(ctx context.Context, issue *Issue) (*Issue, *Response, error) { + return s.UpdateWithOptionsWithContext(ctx, issue, nil) +} + +// Update wraps UpdateWithContext using the background context. +func (s *IssueService) Update(issue *Issue) (*Issue, *Response, error) { + return s.UpdateWithContext(context.Background(), issue) +} + +// UpdateIssueWithContext updates an issue from a JSON representation. The issue is found by key. +// +// https://docs.atlassian.com/jira/REST/7.4.0/#api/2/issue-editIssue +// Caller must close resp.Body +func (s *IssueService) UpdateIssueWithContext(ctx context.Context, jiraID string, data map[string]interface{}) (*Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", jiraID) + req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, data) + if err != nil { + return nil, err + } + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + // This is just to follow the rest of the API's convention of returning an issue. + // Returning the same pointer here is pointless, so we return a copy instead. + return resp, nil +} + +// UpdateIssue wraps UpdateIssueWithContext using the background context. +// Caller must close resp.Body +func (s *IssueService) UpdateIssue(jiraID string, data map[string]interface{}) (*Response, error) { + return s.UpdateIssueWithContext(context.Background(), jiraID, data) +} + +// AddCommentWithContext adds a new comment to issueID. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-addComment +func (s *IssueService) AddCommentWithContext(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment", issueID) + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, comment) + if err != nil { + return nil, nil, err + } + + responseComment := new(Comment) + resp, err := s.client.Do(req, responseComment) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return responseComment, resp, nil +} + +// AddComment wraps AddCommentWithContext using the background context. +func (s *IssueService) AddComment(issueID string, comment *Comment) (*Comment, *Response, error) { + return s.AddCommentWithContext(context.Background(), issueID, comment) +} + +// UpdateCommentWithContext updates the body of a comment, identified by comment.ID, on the issueID. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/comment-updateComment +func (s *IssueService) UpdateCommentWithContext(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { + reqBody := struct { + Body string `json:"body"` + }{ + Body: comment.Body, + } + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, comment.ID) + req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, reqBody) + if err != nil { + return nil, nil, err + } + + responseComment := new(Comment) + resp, err := s.client.Do(req, responseComment) + if err != nil { + return nil, resp, err + } + + return responseComment, resp, nil +} + +// UpdateComment wraps UpdateCommentWithContext using the background context. +func (s *IssueService) UpdateComment(issueID string, comment *Comment) (*Comment, *Response, error) { + return s.UpdateCommentWithContext(context.Background(), issueID, comment) +} + +// DeleteCommentWithContext Deletes a comment from an issueID. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-issue-issueIdOrKey-comment-id-delete +func (s *IssueService) DeleteCommentWithContext(ctx context.Context, issueID, commentID string) error { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, commentID) + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + if err != nil { + return err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return jerr + } + defer resp.Body.Close() + + return nil +} + +// DeleteComment wraps DeleteCommentWithContext using the background context. +func (s *IssueService) DeleteComment(issueID, commentID string) error { + return s.DeleteCommentWithContext(context.Background(), issueID, commentID) +} + +// AddWorklogRecordWithContext adds a new worklog record to issueID. +// +// https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-worklog-post +func (s *IssueService) AddWorklogRecordWithContext(ctx context.Context, issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, record) + if err != nil { + return nil, nil, err + } + + for _, option := range options { + err = option(req) + if err != nil { + return nil, nil, err + } + } + + responseRecord := new(WorklogRecord) + resp, err := s.client.Do(req, responseRecord) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return responseRecord, resp, nil +} + +// AddWorklogRecord wraps AddWorklogRecordWithContext using the background context. +func (s *IssueService) AddWorklogRecord(issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { + return s.AddWorklogRecordWithContext(context.Background(), issueID, record, options...) +} + +// UpdateWorklogRecordWithContext updates a worklog record. +// +// https://docs.atlassian.com/software/jira/docs/api/REST/7.1.2/#api/2/issue-updateWorklog +func (s *IssueService) UpdateWorklogRecordWithContext(ctx context.Context, issueID, worklogID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog/%s", issueID, worklogID) + req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, record) + if err != nil { + return nil, nil, err + } + + for _, option := range options { + err = option(req) + if err != nil { + return nil, nil, err + } + } + + responseRecord := new(WorklogRecord) + resp, err := s.client.Do(req, responseRecord) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return responseRecord, resp, nil +} + +// UpdateWorklogRecord wraps UpdateWorklogRecordWithContext using the background context. +func (s *IssueService) UpdateWorklogRecord(issueID, worklogID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { + return s.UpdateWorklogRecordWithContext(context.Background(), issueID, worklogID, record, options...) +} + +// AddLinkWithContext adds a link between two issues. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issueLink +// Caller must close resp.Body +func (s *IssueService) AddLinkWithContext(ctx context.Context, issueLink *IssueLink) (*Response, error) { + apiEndpoint := "rest/api/2/issueLink" + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, issueLink) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + err = NewJiraError(resp, err) + } + + return resp, err +} + +// AddLink wraps AddLinkWithContext using the background context. +// Caller must close resp.Body +func (s *IssueService) AddLink(issueLink *IssueLink) (*Response, error) { + return s.AddLinkWithContext(context.Background(), issueLink) +} + +// 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) { + u := url.URL{ + Path: "rest/api/2/search", + } + uv := url.Values{} + if jql != "" { + uv.Add("jql", jql) + } + + if options != nil { + if options.StartAt != 0 { + uv.Add("startAt", strconv.Itoa(options.StartAt)) + } + if options.MaxResults != 0 { + uv.Add("maxResults", strconv.Itoa(options.MaxResults)) + } + if options.Expand != "" { + uv.Add("expand", options.Expand) + } + if strings.Join(options.Fields, ",") != "" { + uv.Add("fields", strings.Join(options.Fields, ",")) + } + if options.ValidateQuery != "" { + uv.Add("validateQuery", options.ValidateQuery) + } + } + + u.RawQuery = uv.Encode() + + req, err := s.client.NewRequestWithContext(ctx, "GET", u.String(), nil) + if err != nil { + return []Issue{}, nil, err + } + + v := new(searchResult) + resp, err := s.client.Do(req, v) + if err != nil { + err = NewJiraError(resp, err) + } + return v.Issues, resp, err +} + +// Search wraps SearchWithContext using the background context. +func (s *IssueService) Search(jql string, options *SearchOptions) ([]Issue, *Response, error) { + return s.SearchWithContext(context.Background(), jql, options) +} + +// SearchPagesWithContext will get issues from all pages in a search +// +// 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) SearchPagesWithContext(ctx context.Context, jql string, options *SearchOptions, f func(Issue) error) error { + if options == nil { + options = &SearchOptions{ + StartAt: 0, + MaxResults: 50, + } + } + + if options.MaxResults == 0 { + options.MaxResults = 50 + } + + issues, resp, err := s.SearchWithContext(ctx, jql, options) + if err != nil { + return err + } + + if len(issues) == 0 { + return nil + } + + for { + for _, issue := range issues { + err = f(issue) + if err != nil { + return err + } + } + + if resp.StartAt+resp.MaxResults >= resp.Total { + return nil + } + + options.StartAt += resp.MaxResults + issues, resp, err = s.SearchWithContext(ctx, jql, options) + if err != nil { + return err + } + } +} + +// SearchPages wraps SearchPagesWithContext using the background context. +func (s *IssueService) SearchPages(jql string, options *SearchOptions, f func(Issue) error) error { + return s.SearchPagesWithContext(context.Background(), jql, options, f) +} + +// GetCustomFieldsWithContext returns a map of customfield_* keys with string values +func (s *IssueService) GetCustomFieldsWithContext(ctx context.Context, issueID string) (CustomFields, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + issue := new(map[string]interface{}) + resp, err := s.client.Do(req, issue) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + m := *issue + f := m["fields"] + cf := make(CustomFields) + if f == nil { + return cf, resp, nil + } + + if rec, ok := f.(map[string]interface{}); ok { + for key, val := range rec { + if strings.Contains(key, "customfield") { + if valMap, ok := val.(map[string]interface{}); ok { + if v, ok := valMap["value"]; ok { + val = v + } + } + cf[key] = fmt.Sprint(val) + } + } + } + return cf, resp, nil +} + +// GetCustomFields wraps GetCustomFieldsWithContext using the background context. +func (s *IssueService) GetCustomFields(issueID string) (CustomFields, *Response, error) { + return s.GetCustomFieldsWithContext(context.Background(), issueID) +} + +// GetTransitionsWithContext gets a list of the transitions possible for this issue by the current user, +// along with fields that are required and their types. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getTransitions +func (s *IssueService) GetTransitionsWithContext(ctx context.Context, id string) ([]Transition, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions?expand=transitions.fields", id) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + result := new(transitionResult) + resp, err := s.client.Do(req, result) + if err != nil { + err = NewJiraError(resp, err) + } + return result.Transitions, resp, err +} + +// GetTransitions wraps GetTransitionsWithContext using the background context. +func (s *IssueService) GetTransitions(id string) ([]Transition, *Response, error) { + return s.GetTransitionsWithContext(context.Background(), id) +} + +// DoTransitionWithContext performs a transition on an issue. +// When performing the transition you can update or set other issue fields. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition +func (s *IssueService) DoTransitionWithContext(ctx context.Context, ticketID, transitionID string) (*Response, error) { + payload := CreateTransitionPayload{ + Transition: TransitionPayload{ + ID: transitionID, + }, + } + return s.DoTransitionWithPayloadWithContext(ctx, ticketID, payload) +} + +// DoTransition wraps DoTransitionWithContext using the background context. +func (s *IssueService) DoTransition(ticketID, transitionID string) (*Response, error) { + return s.DoTransitionWithContext(context.Background(), ticketID, transitionID) +} + +// DoTransitionWithPayloadWithContext performs a transition on an issue using any payload. +// When performing the transition you can update or set other issue fields. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition +// Caller must close resp.Body +func (s *IssueService) DoTransitionWithPayloadWithContext(ctx context.Context, ticketID, payload interface{}) (*Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions", ticketID) + + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + err = NewJiraError(resp, err) + } + + return resp, err +} + +// DoTransitionWithPayload wraps DoTransitionWithPayloadWithContext using the background context. +// Caller must close resp.Body +func (s *IssueService) DoTransitionWithPayload(ticketID, payload interface{}) (*Response, error) { + return s.DoTransitionWithPayloadWithContext(context.Background(), ticketID, payload) +} + +// InitIssueWithMetaAndFields returns Issue with with values from fieldsConfig properly set. +// - metaProject should contain metaInformation about the project where the issue should be created. +// - metaIssuetype is the MetaInformation about the Issuetype that needs to be created. +// - fieldsConfig is a key->value pair where key represents the name of the field as seen in the UI +// And value is the string value for that particular key. +// +// Note: This method doesn't verify that the fieldsConfig is complete with mandatory fields. The fieldsConfig is +// +// supposed to be already verified with MetaIssueType.CheckCompleteAndAvailable. It will however return +// error if the key is not found. +// All values will be packed into Unknowns. This is much convenient. If the struct fields needs to be +// configured as well, marshalling and unmarshalling will set the proper fields. +func InitIssueWithMetaAndFields(metaProject *MetaProject, metaIssuetype *MetaIssueType, fieldsConfig map[string]string) (*Issue, error) { + issue := new(Issue) + issueFields := new(IssueFields) + issueFields.Unknowns = tcontainer.NewMarshalMap() + + // map the field names the User presented to jira's internal key + allFields, _ := metaIssuetype.GetAllFields() + for key, value := range fieldsConfig { + jiraKey, found := allFields[key] + if !found { + return nil, fmt.Errorf("key %s is not found in the list of fields", key) + } + + valueType, err := metaIssuetype.Fields.String(jiraKey + "/schema/type") + if err != nil { + return nil, err + } + switch valueType { + case "array": + elemType, err := metaIssuetype.Fields.String(jiraKey + "/schema/items") + if err != nil { + return nil, err + } + switch elemType { + case "component": + issueFields.Unknowns[jiraKey] = []Component{{Name: value}} + case "option": + issueFields.Unknowns[jiraKey] = []map[string]string{{"value": value}} + default: + issueFields.Unknowns[jiraKey] = []string{value} + } + case "string": + issueFields.Unknowns[jiraKey] = value + case "date": + issueFields.Unknowns[jiraKey] = value + case "datetime": + issueFields.Unknowns[jiraKey] = value + case "any": + // Treat any as string + issueFields.Unknowns[jiraKey] = value + case "project": + issueFields.Unknowns[jiraKey] = Project{ + Name: metaProject.Name, + ID: metaProject.Id, + } + case "priority": + issueFields.Unknowns[jiraKey] = Priority{Name: value} + case "user": + issueFields.Unknowns[jiraKey] = User{ + Name: value, + } + case "issuetype": + issueFields.Unknowns[jiraKey] = IssueType{ + Name: value, + } + case "option": + issueFields.Unknowns[jiraKey] = Option{ + Value: value, + } + default: + return nil, fmt.Errorf("unknown issue type encountered: %s for %s", valueType, key) + } + } + + issue.Fields = issueFields + + return issue, nil +} + +// DeleteWithContext will delete a specified issue. +// Caller must close resp.Body +func (s *IssueService) DeleteWithContext(ctx context.Context, issueID string) (*Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) + + // to enable deletion of subtasks; without this, the request will fail if the issue has subtasks + deletePayload := make(map[string]interface{}) + deletePayload["deleteSubtasks"] = "true" + content, _ := json.Marshal(deletePayload) + + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, content) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + return resp, err +} + +// Delete wraps DeleteWithContext using the background context. +// Caller must close resp.Body +func (s *IssueService) Delete(issueID string) (*Response, error) { + return s.DeleteWithContext(context.Background(), issueID) +} + +// GetWatchersWithContext wil return all the users watching/observing the given issue +// +// Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-getIssueWatchers +func (s *IssueService) GetWatchersWithContext(ctx context.Context, issueID string) (*[]User, *Response, error) { + watchesAPIEndpoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) + + req, err := s.client.NewRequestWithContext(ctx, "GET", watchesAPIEndpoint, nil) + if err != nil { + return nil, nil, err + } + + watches := new(Watches) + resp, err := s.client.Do(req, watches) + if err != nil { + return nil, nil, NewJiraError(resp, err) + } + + result := []User{} + for _, watcher := range watches.Watchers { + var user *User + if watcher.AccountID != "" { + user, resp, err = s.client.User.GetByAccountID(watcher.AccountID) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + } + result = append(result, *user) + } + + return &result, resp, nil +} + +// GetWatchers wraps GetWatchersWithContext using the background context. +func (s *IssueService) GetWatchers(issueID string) (*[]User, *Response, error) { + return s.GetWatchersWithContext(context.Background(), issueID) +} + +// AddWatcherWithContext adds watcher to the given issue +// +// Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-addWatcher +// Caller must close resp.Body +func (s *IssueService) AddWatcherWithContext(ctx context.Context, issueID string, userName string) (*Response, error) { + apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) + + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, userName) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + err = NewJiraError(resp, err) + } + + return resp, err +} + +// AddWatcher wraps AddWatcherWithContext using the background context. +// Caller must close resp.Body +func (s *IssueService) AddWatcher(issueID string, userName string) (*Response, error) { + return s.AddWatcherWithContext(context.Background(), issueID, userName) +} + +// RemoveWatcherWithContext removes given user from given issue +// +// Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-removeWatcher +// Caller must close resp.Body +func (s *IssueService) RemoveWatcherWithContext(ctx context.Context, issueID string, userName string) (*Response, error) { + apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) + + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, userName) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + err = NewJiraError(resp, err) + } + + return resp, err +} + +// RemoveWatcher wraps RemoveWatcherWithContext using the background context. +// Caller must close resp.Body +func (s *IssueService) RemoveWatcher(issueID string, userName string) (*Response, error) { + return s.RemoveWatcherWithContext(context.Background(), issueID, userName) +} + +// UpdateAssigneeWithContext updates the user assigned to work on the given issue +// +// Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.2/#api/2/issue-assign +// Caller must close resp.Body +func (s *IssueService) UpdateAssigneeWithContext(ctx context.Context, issueID string, assignee *User) (*Response, error) { + apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/assignee", issueID) + + req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndPoint, assignee) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + err = NewJiraError(resp, err) + } + + return resp, err +} + +// UpdateAssignee wraps UpdateAssigneeWithContext using the background context. +// Caller must close resp.Body +func (s *IssueService) UpdateAssignee(issueID string, assignee *User) (*Response, error) { + return s.UpdateAssigneeWithContext(context.Background(), issueID, assignee) +} + +func (c ChangelogHistory) CreatedTime() (time.Time, error) { + var t time.Time + // Ignore null + if string(c.Created) == "null" { + return t, nil + } + t, err := time.Parse("2006-01-02T15:04:05.999-0700", c.Created) + return t, err +} + +// GetRemoteLinksWithContext gets remote issue links on the issue. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getRemoteIssueLinks +func (s *IssueService) GetRemoteLinksWithContext(ctx context.Context, id string) (*[]RemoteLink, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", id) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + result := new([]RemoteLink) + resp, err := s.client.Do(req, result) + if err != nil { + err = NewJiraError(resp, err) + } + return result, resp, err +} + +// GetRemoteLinks wraps GetRemoteLinksWithContext using the background context. +// Caller must close resp.Body +func (s *IssueService) GetRemoteLinks(id string) (*[]RemoteLink, *Response, error) { + return s.GetRemoteLinksWithContext(context.Background(), id) +} + +// AddRemoteLinkWithContext adds a remote link to issueID. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-remotelink-post +func (s *IssueService) AddRemoteLinkWithContext(ctx context.Context, issueID string, remotelink *RemoteLink) (*RemoteLink, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", issueID) + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, remotelink) + if err != nil { + return nil, nil, err + } + + responseRemotelink := new(RemoteLink) + resp, err := s.client.Do(req, responseRemotelink) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return responseRemotelink, resp, nil +} + +// AddRemoteLink wraps AddRemoteLinkWithContext using the background context. +func (s *IssueService) AddRemoteLink(issueID string, remotelink *RemoteLink) (*RemoteLink, *Response, error) { + return s.AddRemoteLinkWithContext(context.Background(), issueID, remotelink) +} + +// UpdateRemoteLinkWithContext updates a remote issue link by linkID. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-remote-links/#api-rest-api-2-issue-issueidorkey-remotelink-linkid-put +func (s *IssueService) UpdateRemoteLinkWithContext(ctx context.Context, issueID string, linkID int, remotelink *RemoteLink) (*Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink/%d", issueID, linkID) + req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, remotelink) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return resp, jerr + } + + return resp, nil +} + +// UpdateRemoteLink wraps UpdateRemoteLinkWithContext using the background context. +func (s *IssueService) UpdateRemoteLink(issueID string, linkID int, remotelink *RemoteLink) (*Response, error) { + return s.UpdateRemoteLinkWithContext(context.Background(), issueID, linkID, remotelink) +} diff --git a/onpremise/issue_test.go b/onpremise/issue_test.go new file mode 100644 index 0000000..5816f67 --- /dev/null +++ b/onpremise/issue_test.go @@ -0,0 +1,1933 @@ +package onpremise + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "reflect" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/trivago/tgo/tcontainer" +) + +func TestIssueService_Get_Success(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/issue/10002") + + fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) + }) + + issue, _, err := testClient.Issue.Get("10002", nil) + if issue == nil { + t.Error("Expected issue. Issue is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_Get_WithQuerySuccess(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/issue/10002?expand=foo") + + fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) + }) + + opt := &GetQueryOptions{ + Expand: "foo", + } + issue, _, err := testClient.Issue.Get("10002", opt) + if issue == nil { + t.Error("Expected issue. Issue is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_Create(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/issue") + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) + }) + + i := &Issue{ + Fields: &IssueFields{ + Description: "example bug report", + }, + } + issue, _, err := testClient.Issue.Create(i) + if issue == nil { + t.Error("Expected issue. Issue is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_CreateThenGet(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/issue") + + w.WriteHeader(http.StatusCreated) + io.Copy(w, r.Body) + }) + + i := &Issue{ + Fields: &IssueFields{ + Description: "example bug report", + Created: Time(time.Now()), + }, + } + issue, _, err := testClient.Issue.Create(i) + if issue == nil { + t.Error("Expected issue. Issue is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } + + testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/issue/10002") + + bytes, err := json.Marshal(issue) + if err != nil { + t.Errorf("Error marshaling issue: %s", err) + } + _, err = w.Write(bytes) + if err != nil { + t.Errorf("Error writing response: %s", err) + } + }) + + issue2, _, err := testClient.Issue.Get("10002", nil) + if issue2 == nil { + t.Error("Expected issue. Issue is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_Update(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/PROJ-9001", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testRequestURL(t, r, "/rest/api/2/issue/PROJ-9001") + + w.WriteHeader(http.StatusNoContent) + }) + + i := &Issue{ + Key: "PROJ-9001", + Fields: &IssueFields{ + Description: "example bug report", + }, + } + issue, _, err := testClient.Issue.Update(i) + if issue == nil { + t.Error("Expected issue. Issue is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_UpdateIssue(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/PROJ-9001", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testRequestURL(t, r, "/rest/api/2/issue/PROJ-9001") + + w.WriteHeader(http.StatusNoContent) + }) + jID := "PROJ-9001" + i := make(map[string]interface{}) + fields := make(map[string]interface{}) + i["fields"] = fields + resp, err := testClient.Issue.UpdateIssue(jID, i) + if resp == nil { + t.Error("Expected resp. resp is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } + +} + +func TestIssueService_AddComment(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10000/comment", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/issue/10000/comment") + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}`) + }) + + c := &Comment{ + Body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.", + Visibility: CommentVisibility{ + Type: "role", + Value: "Administrators", + }, + } + comment, _, err := testClient.Issue.AddComment("10000", c) + if comment == nil { + t.Error("Expected Comment. Comment is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_UpdateComment(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10000/comment/10001", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testRequestURL(t, r, "/rest/api/2/issue/10000/comment/10001") + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10001","id":"10001","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}`) + }) + + c := &Comment{ + ID: "10001", + Body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.", + Visibility: CommentVisibility{ + Type: "role", + Value: "Administrators", + }, + } + comment, _, err := testClient.Issue.UpdateComment("10000", c) + if comment == nil { + t.Error("Expected Comment. Comment is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_DeleteComment(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10000/comment/10001", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, "/rest/api/2/issue/10000/comment/10001") + + w.WriteHeader(http.StatusNoContent) + fmt.Fprint(w, `{}`) + }) + + err := testClient.Issue.DeleteComment("10000", "10001") + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_AddWorklogRecord(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10000/worklog", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/issue/10000/worklog") + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2018-02-14T22:14:46.003+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2018-02-14T22:14:46.003+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}`) + }) + r := &WorklogRecord{ + TimeSpent: "1h", + } + record, _, err := testClient.Issue.AddWorklogRecord("10000", r) + if record == nil { + t.Error("Expected Record. Record is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_UpdateWorklogRecord(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10000/worklog/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testRequestURL(t, r, "/rest/api/2/issue/10000/worklog/1") + + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/10000/worklog/1","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2018-02-14T22:14:46.003+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2018-02-14T22:14:46.003+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}`) + }) + r := &WorklogRecord{ + TimeSpent: "1h", + } + record, _, err := testClient.Issue.UpdateWorklogRecord("10000", "1", r) + if record == nil { + t.Error("Expected Record. Record is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_AddLink(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issueLink", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/issueLink") + + w.WriteHeader(http.StatusOK) + }) + + il := &IssueLink{ + Type: IssueLinkType{ + Name: "Duplicate", + }, + InwardIssue: &Issue{ + Key: "HSP-1", + }, + OutwardIssue: &Issue{ + Key: "MKY-1", + }, + Comment: &Comment{ + Body: "Linked related issue!", + Visibility: CommentVisibility{ + Type: "group", + Value: "jira-software-users", + }, + }, + } + resp, err := testClient.Issue.AddLink(il) + if err != nil { + t.Errorf("Error given: %s", err) + } + if resp == nil { + t.Error("Expected response. Response is nil") + return + } + if resp.StatusCode != 200 { + t.Errorf("Expected Status code 200. Given %d", resp.StatusCode) + } +} + +func TestIssueService_Get_Fields(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/issue/10002") + + fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) + }) + + issue, _, err := testClient.Issue.Get("10002", nil) + if err != nil { + t.Errorf("Error given: %s", err) + } + if issue == nil { + t.Error("Expected issue. Issue is nil") + return + } + if !reflect.DeepEqual(issue.Fields.Labels, []string{"test"}) { + t.Error("Expected labels for the returned issue") + } + + if len(issue.Fields.Comments.Comments) != 1 { + t.Errorf("Expected one comment, %v found", len(issue.Fields.Comments.Comments)) + } + if issue.Fields.Epic == nil { + t.Error("Epic expected but not found") + } +} + +func TestIssueService_Get_RenderedFields(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/issue/10002") + + fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{},"renderedFields":{"resolutiondate":"In 1 week","updated":"2 hours ago","comment":{"comments":[{"body":"This is HTML"}]}}}`) + }) + + issue, _, err := testClient.Issue.Get("10002", nil) + if err != nil { + t.Errorf("Error given: %s", err) + } + if issue == nil { + t.Error("Expected issue. Issue is nil") + return + } + if issue.RenderedFields.Updated != "2 hours ago" { + t.Error("Expected updated to equla '2 hours ago' for rendered field") + } + + if len(issue.RenderedFields.Comments.Comments) != 1 { + t.Errorf("Expected one comment, %v found", len(issue.RenderedFields.Comments.Comments)) + } + comment := issue.RenderedFields.Comments.Comments[0] + if comment.Body != "This is HTML" { + t.Errorf("Wrong comment body returned in RenderedField. Got %s", comment.Body) + } +} + +func TestIssueService_DownloadAttachment(t *testing.T) { + var testAttachment = "Here is an attachment" + + setup() + defer teardown() + testMux.HandleFunc("/secure/attachment/", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/secure/attachment/10000/") + + w.WriteHeader(http.StatusOK) + w.Write([]byte(testAttachment)) + }) + + resp, err := testClient.Issue.DownloadAttachment("10000") + if err != nil { + t.Errorf("Error given: %s", err) + } + if resp == nil { + t.Error("Expected response. Response is nil") + return + } + defer resp.Body.Close() + + attachment, err := io.ReadAll(resp.Body) + if err != nil { + t.Error("Expected attachment text", err) + } + if string(attachment) != testAttachment { + t.Errorf("Expecting an attachment: %s", string(attachment)) + } + + if resp.StatusCode != 200 { + t.Errorf("Expected Status code 200. Given %d", resp.StatusCode) + } +} + +func TestIssueService_DownloadAttachment_BadStatus(t *testing.T) { + + setup() + defer teardown() + testMux.HandleFunc("/secure/attachment/", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/secure/attachment/10000/") + + w.WriteHeader(http.StatusForbidden) + }) + + resp, err := testClient.Issue.DownloadAttachment("10000") + if resp == nil { + t.Error("Expected response. Response is nil") + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusForbidden { + t.Errorf("Expected Status code %d. Given %d", http.StatusForbidden, resp.StatusCode) + } + if err == nil { + t.Errorf("Error expected") + } +} + +func TestIssueService_PostAttachment(t *testing.T) { + var testAttachment = "Here is an attachment" + + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") + status := http.StatusOK + + file, _, err := r.FormFile("file") + if err != nil { + status = http.StatusNotAcceptable + } + defer file.Close() + + if file == nil { + status = http.StatusNoContent + } else { + // Read the file into memory + data, err := io.ReadAll(file) + if err != nil { + status = http.StatusInternalServerError + } + if string(data) != testAttachment { + status = http.StatusNotAcceptable + } + } + w.WriteHeader(status) + fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) + }) + + reader := strings.NewReader(testAttachment) + + issue, resp, err := testClient.Issue.PostAttachment("10000", reader, "attachment") + + if issue == nil { + t.Error("Expected response. Response is nil") + } + + if resp.StatusCode != 200 { + t.Errorf("Expected Status code 200. Given %d", resp.StatusCode) + } + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_PostAttachment_NoResponse(t *testing.T) { + var testAttachment = "Here is an attachment" + + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") + w.WriteHeader(http.StatusOK) + }) + reader := strings.NewReader(testAttachment) + + _, _, err := testClient.Issue.PostAttachment("10000", reader, "attachment") + + if err == nil { + t.Errorf("Error expected: %s", err) + } +} + +func TestIssueService_PostAttachment_NoFilename(t *testing.T) { + var testAttachment = "Here is an attachment" + + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) + }) + reader := strings.NewReader(testAttachment) + + _, _, err := testClient.Issue.PostAttachment("10000", reader, "") + + if err != nil { + t.Errorf("Error expected: %s", err) + } +} + +func TestIssueService_PostAttachment_NoAttachment(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) + }) + + _, _, err := testClient.Issue.PostAttachment("10000", nil, "attachment") + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_DeleteAttachment(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/attachment/10054", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, "/rest/api/2/attachment/10054") + + w.WriteHeader(http.StatusNoContent) + fmt.Fprint(w, `{}`) + }) + + resp, err := testClient.Issue.DeleteAttachment("10054") + if resp.StatusCode != 204 { + t.Error("Expected attachment not deleted.") + if resp.StatusCode == 403 { + t.Error("User not permitted to delete attachment") + } + if resp.StatusCode == 404 { + t.Error("Attachment not found") + } + } + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_DeleteLink(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issueLink/10054", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, "/rest/api/2/issueLink/10054") + + w.WriteHeader(http.StatusNoContent) + fmt.Fprint(w, `{}`) + }) + + resp, err := testClient.Issue.DeleteLink("10054") + if resp.StatusCode != 204 { + t.Error("Expected link not deleted.") + if resp.StatusCode == 403 { + t.Error("User not permitted to delete link") + } + if resp.StatusCode == 404 { + t.Error("Link not found") + } + } + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_Search(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=type+%3D+Bug+and+Status+NOT+IN+%28Resolved%29&maxResults=40&startAt=1") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 40,"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}}}]}`) + }) + + opt := &SearchOptions{StartAt: 1, MaxResults: 40, Expand: "foo"} + _, resp, err := testClient.Issue.Search("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 resp.StartAt != 1 { + t.Errorf("StartAt should populate with 1, %v given", resp.StartAt) + } + if resp.MaxResults != 40 { + t.Errorf("MaxResults should populate with 40, %v given", resp.MaxResults) + } + if resp.Total != 6 { + t.Errorf("Total should populate with 6, %v given", resp.Total) + } +} + +func TestIssueService_SearchEmptyJQL(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&maxResults=40&startAt=1") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 40,"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}}}]}`) + }) + + opt := &SearchOptions{StartAt: 1, MaxResults: 40, Expand: "foo"} + _, resp, err := testClient.Issue.Search("", opt) + + if resp == nil { + t.Errorf("Response given: %+v", resp) + } + if err != nil { + t.Errorf("Error given: %s", err) + } + + if resp.StartAt != 1 { + t.Errorf("StartAt should populate with 1, %v given", resp.StartAt) + } + if resp.MaxResults != 40 { + t.Errorf("StartAt should populate with 40, %v given", resp.MaxResults) + } + if resp.Total != 6 { + t.Errorf("StartAt should populate with 6, %v given", resp.Total) + } +} + +func TestIssueService_Search_WithoutPaging(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?jql=something") + 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) + + if resp == nil { + t.Errorf("Response given: %+v", resp) + } + if err != nil { + t.Errorf("Error given: %s", err) + } + + if resp.StartAt != 0 { + t.Errorf("StartAt should populate with 0, %v given", resp.StartAt) + } + if resp.MaxResults != 50 { + t.Errorf("StartAt should populate with 50, %v given", resp.MaxResults) + } + if resp.Total != 6 { + t.Errorf("StartAt should populate with 6, %v given", resp.Total) + } +} + +func TestIssueService_SearchPages(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + if r.URL.String() == "/rest/api/2/search?expand=foo&jql=something&maxResults=2&startAt=1&validateQuery=warn" { + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 2,"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}}}]}`) + return + } else if r.URL.String() == "/rest/api/2/search?expand=foo&jql=something&maxResults=2&startAt=3&validateQuery=warn" { + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"expand": "schema,names","startAt": 3,"maxResults": 2,"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}}}]}`) + return + } else if r.URL.String() == "/rest/api/2/search?expand=foo&jql=something&maxResults=2&startAt=5&validateQuery=warn" { + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"expand": "schema,names","startAt": 5,"maxResults": 2,"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}}]}`) + return + } + + t.Errorf("Unexpected URL: %v", r.URL) + }) + + opt := &SearchOptions{StartAt: 1, MaxResults: 2, Expand: "foo", ValidateQuery: "warn"} + issues := make([]Issue, 0) + err := testClient.Issue.SearchPages("something", opt, func(issue Issue) error { + issues = append(issues, issue) + return nil + }) + + if err != nil { + t.Errorf("Error given: %s", err) + } + + if len(issues) != 5 { + t.Errorf("Expected 5 issues, %v given", len(issues)) + } +} + +func TestIssueService_SearchPages_EmptyResult(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + if r.URL.String() == "/rest/api/2/search?expand=foo&jql=something&maxResults=50&startAt=1&validateQuery=warn" { + w.WriteHeader(http.StatusOK) + // This is what Jira outputs when the &maxResult= issue occurs. It used to cause SearchPages to go into an endless loop. + fmt.Fprint(w, `{"expand": "schema,names","startAt": 0,"maxResults": 0,"total": 6,"issues": []}`) + return + } + + t.Errorf("Unexpected URL: %v", r.URL) + }) + + opt := &SearchOptions{StartAt: 1, MaxResults: 50, Expand: "foo", ValidateQuery: "warn"} + issues := make([]Issue, 0) + err := testClient.Issue.SearchPages("something", opt, func(issue Issue) error { + issues = append(issues, issue) + return nil + }) + + if err != nil { + t.Errorf("Error given: %s", err) + } + +} + +func TestIssueService_GetCustomFields(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/issue/10002") + fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"customfield_123":"test","watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) + }) + + issue, _, err := testClient.Issue.GetCustomFields("10002") + if err != nil { + t.Errorf("Error given: %s", err) + } + if issue == nil { + t.Error("Expected Customfields") + } + cf := issue["customfield_123"] + if cf != "test" { + t.Error("Expected \"test\" for custom field") + } +} + +func TestIssueService_GetComplexCustomFields(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/issue/10002") + fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"customfield_123":{"self":"http://www.example.com/jira/rest/api/2/customFieldOption/123","value":"test","id":"123"},"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) + }) + + issue, _, err := testClient.Issue.GetCustomFields("10002") + if err != nil { + t.Errorf("Error given: %s", err) + } + if issue == nil { + t.Error("Expected Customfields") + } + cf := issue["customfield_123"] + if cf != "test" { + t.Error("Expected \"test\" for custom field") + } +} + +func TestIssueService_GetTransitions(t *testing.T) { + setup() + defer teardown() + + testAPIEndpoint := "/rest/api/2/issue/123/transitions" + + raw, err := os.ReadFile("../testing/mock-data/transitions.json") + if err != nil { + t.Error(err.Error()) + } + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + transitions, _, err := testClient.Issue.GetTransitions("123") + + if err != nil { + t.Errorf("Got error: %v", err) + } + + if transitions == nil { + t.Error("Expected transition list. Got nil.") + } + + if len(transitions) != 2 { + t.Errorf("Expected 2 transitions. Got %d", len(transitions)) + } + + if transitions[0].Fields["summary"].Required != false { + t.Errorf("First transition summary field should not be required") + } +} + +func TestIssueService_DoTransition(t *testing.T) { + setup() + defer teardown() + + testAPIEndpoint := "/rest/api/2/issue/123/transitions" + + transitionID := "22" + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, testAPIEndpoint) + + decoder := json.NewDecoder(r.Body) + var payload CreateTransitionPayload + err := decoder.Decode(&payload) + if err != nil { + t.Errorf("Got error: %v", err) + } + + if payload.Transition.ID != transitionID { + t.Errorf("Expected %s to be in payload, got %s instead", transitionID, payload.Transition.ID) + } + }) + _, err := testClient.Issue.DoTransition("123", transitionID) + + if err != nil { + t.Errorf("Got error: %v", err) + } +} + +func TestIssueService_DoTransitionWithPayload(t *testing.T) { + setup() + defer teardown() + + testAPIEndpoint := "/rest/api/2/issue/123/transitions" + + transitionID := "22" + + customPayload := map[string]interface{}{ + "update": map[string]interface{}{ + "comment": []map[string]interface{}{ + { + "add": map[string]string{ + "body": "Hello World", + }, + }, + }, + }, + "transition": TransitionPayload{ + ID: transitionID, + }, + } + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, testAPIEndpoint) + + decoder := json.NewDecoder(r.Body) + payload := map[string]interface{}{} + err := decoder.Decode(&payload) + if err != nil { + t.Errorf("Got error: %v", err) + } + + contains := func(key string) bool { + _, ok := payload[key] + return ok + } + + if !contains("update") || !contains("transition") { + t.Fatalf("Excpected update, transition to be in payload, got %s instead", payload) + } + + transition, ok := payload["transition"].(map[string]interface{}) + if !ok { + t.Fatalf("Excpected transition to be in payload, got %s instead", payload["transition"]) + } + + if transition["id"].(string) != transitionID { + t.Errorf("Expected %s to be in payload, got %s instead", transitionID, transition["id"]) + } + }) + _, err := testClient.Issue.DoTransitionWithPayload("123", customPayload) + + if err != nil { + t.Errorf("Got error: %v", err) + } +} + +func TestIssueFields_TestMarshalJSON_PopulateUnknownsSuccess(t *testing.T) { + data := `{ + "customfield_123":"test", + "description":"example bug report", + "project":{ + "self":"http://www.example.com/jira/rest/api/2/project/EX", + "id":"10000", + "key":"EX", + "name":"Example", + "avatarUrls":{ + "48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000", + "24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000", + "16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000", + "32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000" + }, + "projectCategory":{ + "self":"http://www.example.com/jira/rest/api/2/projectCategory/10000", + "id":"10000", + "name":"FIRST", + "description":"First Project Category" + } + }, + "issuelinks":[ + { + "id":"10001", + "type":{ + "id":"10000", + "name":"Dependent", + "inward":"depends on", + "outward":"is depended by" + }, + "outwardIssue":{ + "id":"10004L", + "key":"PRJ-2", + "self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2", + "fields":{ + "status":{ + "iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png", + "name":"Open" + } + } + } + }, + { + "id":"10002", + "type":{ + "id":"10000", + "name":"Dependent", + "inward":"depends on", + "outward":"is depended by" + }, + "inwardIssue":{ + "id":"10004", + "key":"PRJ-3", + "self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3", + "fields":{ + "status":{ + "iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png", + "name":"Open" + } + } + } + } + ] + + }` + + i := new(IssueFields) + err := json.Unmarshal([]byte(data), i) + if err != nil { + t.Errorf("Expected nil error, received %s", err) + } + + if len(i.Unknowns) != 1 { + t.Errorf("Expected 1 unknown field to be present, received %d", len(i.Unknowns)) + } + if i.Description != "example bug report" { + t.Errorf("Expected description to be \"%s\", received \"%s\"", "example bug report", i.Description) + } + +} + +func TestIssueFields_MarshalJSON_OmitsEmptyFields(t *testing.T) { + i := &IssueFields{ + Description: "blahblah", + Type: IssueType{ + Name: "Story", + }, + Labels: []string{"aws-docker"}, + Parent: &Parent{Key: "FOO-300"}, + } + + rawdata, err := json.Marshal(i) + if err != nil { + t.Errorf("Expected nil err, received %s", err) + } + + // convert json to map and see if unset keys are there + issuef := tcontainer.NewMarshalMap() + err = json.Unmarshal(rawdata, &issuef) + if err != nil { + t.Errorf("Expected nil err, received %s", err) + } + + _, err = issuef.Int("issuetype/avatarId") + if err == nil { + t.Error("Expected non nil error, received nil") + } + + // verify Parent nil values are being omitted + _, err = issuef.String("parent/id") + if err == nil { + t.Error("Expected non nil err, received nil") + } + + // verify that the field that should be there, is. + name, err := issuef.String("issuetype/name") + if err != nil { + t.Errorf("Expected nil err, received %s", err) + } + + if name != "Story" { + t.Errorf("Expected Story, received %s", name) + } +} + +func TestIssueFields_MarshalJSON_Success(t *testing.T) { + i := &IssueFields{ + Description: "example bug report", + Unknowns: tcontainer.MarshalMap{ + "customfield_123": "test", + }, + Project: Project{ + Self: "http://www.example.com/jira/rest/api/2/project/EX", + ID: "10000", + Key: "EX", + }, + AffectsVersions: []*AffectsVersion{ + { + ID: "10705", + Name: "2.1.0-rc3", + Self: "http://www.example.com/jira/rest/api/2/version/10705", + ReleaseDate: "2018-09-30", + }, + }, + } + + bytes, err := json.Marshal(i) + if err != nil { + t.Errorf("Expected nil err, received %s", err) + } + + received := new(IssueFields) + // the order of json might be different. so unmarshal it again and compare objects + err = json.Unmarshal(bytes, received) + if err != nil { + t.Errorf("Expected nil err, received %s", err) + } + + if !reflect.DeepEqual(i, received) { + t.Errorf("Received object different from expected. Expected %+v, received %+v", i, received) + } +} + +func TestInitIssueWithMetaAndFields_Success(t *testing.T) { + metaProject := MetaProject{ + Name: "Engineering - Dept", + Id: "ENG", + } + + fields := tcontainer.NewMarshalMap() + fields["summary"] = map[string]interface{}{ + "name": "Summary", + "schema": map[string]interface{}{ + "type": "string", + }, + } + + metaIssueType := MetaIssueType{ + Fields: fields, + } + expectedSummary := "Issue Summary" + fieldConfig := map[string]string{ + "Summary": "Issue Summary", + } + + issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) + if err != nil { + t.Errorf("Expected nil error, received %s", err) + } + + gotSummary, found := issue.Fields.Unknowns["summary"] + if !found { + t.Errorf("Expected summary to be set in issue. Not set.") + } + + if gotSummary != expectedSummary { + t.Errorf("Expected %s received %s", expectedSummary, gotSummary) + } +} + +func TestInitIssueWithMetaAndFields_ArrayValueType(t *testing.T) { + metaProject := MetaProject{ + Name: "Engineering - Dept", + Id: "ENG", + } + + fields := tcontainer.NewMarshalMap() + fields["component"] = map[string]interface{}{ + "name": "Component/s", + "schema": map[string]interface{}{ + "type": "array", + "items": "component", + }, + } + + metaIssueType := MetaIssueType{ + Fields: fields, + } + + expectedComponent := "Jira automation" + fieldConfig := map[string]string{ + "Component/s": expectedComponent, + } + + issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) + if err != nil { + t.Errorf("Expected nil error, received %s", err) + } + + c, isArray := issue.Fields.Unknowns["component"].([]Component) + if isArray == false { + t.Error("Expected array, non array object received") + } + + if len(c) != 1 { + t.Errorf("Expected received array to be of length 1. Got %d", len(c)) + } + + gotComponent := c[0].Name + + if err != nil { + t.Errorf("Expected err to be nil, received %s", err) + } + + if gotComponent != expectedComponent { + t.Errorf("Expected %s received %s", expectedComponent, gotComponent) + } +} + +func TestInitIssueWithMetaAndFields_DateValueType(t *testing.T) { + metaProject := MetaProject{ + Name: "Engineering - Dept", + Id: "ENG", + } + + fields := tcontainer.NewMarshalMap() + fields["created"] = map[string]interface{}{ + "name": "Created", + "schema": map[string]interface{}{ + "type": "date", + }, + } + + metaIssueType := MetaIssueType{ + Fields: fields, + } + + expectedCreated := "19 oct 2012" + fieldConfig := map[string]string{ + "Created": expectedCreated, + } + + issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) + if err != nil { + t.Errorf("Expected nil error, received %s", err) + } + + gotCreated, err := issue.Fields.Unknowns.String("created") + if err != nil { + t.Errorf("Expected err to be nil, received %s", err) + } + + if gotCreated != expectedCreated { + t.Errorf("Expected %s received %s", expectedCreated, gotCreated) + } +} + +func TestInitIssueWithMetaAndFields_UserValueType(t *testing.T) { + metaProject := MetaProject{ + Name: "Engineering - Dept", + Id: "ENG", + } + + fields := tcontainer.NewMarshalMap() + fields["assignee"] = map[string]interface{}{ + "name": "Assignee", + "schema": map[string]interface{}{ + "type": "user", + }, + } + + metaIssueType := MetaIssueType{ + Fields: fields, + } + + expectedAssignee := "jdoe" + fieldConfig := map[string]string{ + "Assignee": expectedAssignee, + } + + issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) + if err != nil { + t.Errorf("Expected nil error, received %s", err) + } + + a, _ := issue.Fields.Unknowns.Value("assignee") + gotAssignee := a.(User).Name + + if gotAssignee != expectedAssignee { + t.Errorf("Expected %s received %s", expectedAssignee, gotAssignee) + } +} + +func TestInitIssueWithMetaAndFields_ProjectValueType(t *testing.T) { + metaProject := MetaProject{ + Name: "Engineering - Dept", + Id: "ENG", + } + + fields := tcontainer.NewMarshalMap() + fields["project"] = map[string]interface{}{ + "name": "Project", + "schema": map[string]interface{}{ + "type": "project", + }, + } + + metaIssueType := MetaIssueType{ + Fields: fields, + } + + setProject := "somewhere" + fieldConfig := map[string]string{ + "Project": setProject, + } + + issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) + if err != nil { + t.Errorf("Expected nil error, received %s", err) + } + + a, _ := issue.Fields.Unknowns.Value("project") + gotProject := a.(Project).Name + + if gotProject != metaProject.Name { + t.Errorf("Expected %s received %s", metaProject.Name, gotProject) + } +} + +func TestInitIssueWithMetaAndFields_PriorityValueType(t *testing.T) { + metaProject := MetaProject{ + Name: "Engineering - Dept", + Id: "ENG", + } + + fields := tcontainer.NewMarshalMap() + fields["priority"] = map[string]interface{}{ + "name": "Priority", + "schema": map[string]interface{}{ + "type": "priority", + }, + } + + metaIssueType := MetaIssueType{ + Fields: fields, + } + + expectedPriority := "Normal" + fieldConfig := map[string]string{ + "Priority": expectedPriority, + } + + issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) + if err != nil { + t.Errorf("Expected nil error, received %s", err) + } + + a, _ := issue.Fields.Unknowns.Value("priority") + gotPriority := a.(Priority).Name + + if gotPriority != expectedPriority { + t.Errorf("Expected %s received %s", expectedPriority, gotPriority) + } +} + +func TestInitIssueWithMetaAndFields_SelectList(t *testing.T) { + metaProject := MetaProject{ + Name: "Engineering - Dept", + Id: "ENG", + } + + fields := tcontainer.NewMarshalMap() + fields["someitem"] = map[string]interface{}{ + "name": "A Select Item", + "schema": map[string]interface{}{ + "type": "option", + }, + } + + metaIssueType := MetaIssueType{ + Fields: fields, + } + + expectedVal := "Value" + fieldConfig := map[string]string{ + "A Select Item": expectedVal, + } + + issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) + if err != nil { + t.Errorf("Expected nil error, received %s", err) + } + + a, _ := issue.Fields.Unknowns.Value("someitem") + gotVal := a.(Option).Value + + if gotVal != expectedVal { + t.Errorf("Expected %s received %s", expectedVal, gotVal) + } +} + +func TestInitIssueWithMetaAndFields_IssuetypeValueType(t *testing.T) { + metaProject := MetaProject{ + Name: "Engineering - Dept", + Id: "ENG", + } + + fields := tcontainer.NewMarshalMap() + fields["issuetype"] = map[string]interface{}{ + "name": "Issue type", + "schema": map[string]interface{}{ + "type": "issuetype", + }, + } + + metaIssueType := MetaIssueType{ + Fields: fields, + } + + expectedIssuetype := "Bug" + fieldConfig := map[string]string{ + "Issue type": expectedIssuetype, + } + + issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) + if err != nil { + t.Errorf("Expected nil error, received %s", err) + } + + a, _ := issue.Fields.Unknowns.Value("issuetype") + gotIssuetype := a.(IssueType).Name + + if gotIssuetype != expectedIssuetype { + t.Errorf("Expected %s received %s", expectedIssuetype, gotIssuetype) + } +} + +func TestInitIssueWithmetaAndFields_FailureWithUnknownValueType(t *testing.T) { + metaProject := MetaProject{ + Name: "Engineering - Dept", + Id: "ENG", + } + + fields := tcontainer.NewMarshalMap() + fields["issuetype"] = map[string]interface{}{ + "name": "Issue type", + "schema": map[string]interface{}{ + "type": "randomType", + }, + } + + metaIssueType := MetaIssueType{ + Fields: fields, + } + + fieldConfig := map[string]string{ + "Issue tyoe": "sometype", + } + _, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) + if err == nil { + t.Error("Expected non nil error, received nil") + } + +} + +func TestIssueService_Delete(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, "/rest/api/2/issue/10002") + + w.WriteHeader(http.StatusNoContent) + fmt.Fprint(w, `{}`) + }) + + resp, err := testClient.Issue.Delete("10002") + if resp.StatusCode != 204 { + t.Error("Expected issue not deleted.") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func getTime(original time.Time) *Time { + jiraTime := Time(original) + + return &jiraTime +} + +func TestIssueService_GetWorklogs(t *testing.T) { + setup() + defer teardown() + + tt := []struct { + name string + response string + issueId string + uri string + worklog *Worklog + err error + option *AddWorklogQueryOptions + }{ + { + name: "simple worklog", + response: `{"startAt": 1,"maxResults": 40,"total": 1,"worklogs": [{"id": "3","self": "http://kelpie9:8081/rest/api/2/issue/10002/worklog/3","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","comment":"","started":"2016-03-16T04:22:37.356+0000","timeSpent": "1h","timeSpentSeconds": 3600,"issueId":"10002"}]}`, + issueId: "10002", + uri: "/rest/api/2/issue/%s/worklog", + worklog: &Worklog{ + StartAt: 1, + MaxResults: 40, + Total: 1, + Worklogs: []WorklogRecord{ + { + Self: "http://kelpie9:8081/rest/api/2/issue/10002/worklog/3", + Author: &User{ + Self: "http://www.example.com/jira/rest/api/2/user?username=fred", + Name: "fred", + DisplayName: "Fred F. User", + }, + UpdateAuthor: &User{ + Self: "http://www.example.com/jira/rest/api/2/user?username=fred", + Name: "fred", + DisplayName: "Fred F. User", + }, + Created: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + Started: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + Updated: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + TimeSpent: "1h", + TimeSpentSeconds: 3600, + ID: "3", + IssueID: "10002", + }, + }, + }, + }, + { + name: "expanded worklog", + response: `{"startAt":1,"maxResults":40,"total":1,"worklogs":[{"id":"3","self":"http://kelpie9:8081/rest/api/2/issue/10002/worklog/3","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","comment":"","started":"2016-03-16T04:22:37.356+0000","timeSpent":"1h","timeSpentSeconds":3600,"issueId":"10002","properties":[{"key":"foo","value":{"bar":"baz"}}]}]}`, + issueId: "10002", + uri: "/rest/api/2/issue/%s/worklog?expand=properties", + worklog: &Worklog{ + StartAt: 1, + MaxResults: 40, + Total: 1, + Worklogs: []WorklogRecord{ + { + Self: "http://kelpie9:8081/rest/api/2/issue/10002/worklog/3", + Author: &User{ + Self: "http://www.example.com/jira/rest/api/2/user?username=fred", + Name: "fred", + DisplayName: "Fred F. User", + }, + UpdateAuthor: &User{ + Self: "http://www.example.com/jira/rest/api/2/user?username=fred", + Name: "fred", + DisplayName: "Fred F. User", + }, + Created: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + Started: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + Updated: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + TimeSpent: "1h", + TimeSpentSeconds: 3600, + ID: "3", + IssueID: "10002", + Properties: []EntityProperty{ + { + Key: "foo", + Value: map[string]interface{}{ + "bar": "baz", + }, + }, + }, + }, + }, + }, + option: &AddWorklogQueryOptions{Expand: "properties"}, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + uri := fmt.Sprintf(tc.uri, tc.issueId) + testMux.HandleFunc(uri, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, uri) + _, _ = fmt.Fprint(w, tc.response) + }) + + var worklog *Worklog + var err error + + if tc.option != nil { + worklog, _, err = testClient.Issue.GetWorklogs(tc.issueId, WithQueryOptions(tc.option)) + } else { + worklog, _, err = testClient.Issue.GetWorklogs(tc.issueId) + } + + if err != nil && !cmp.Equal(err, tc.err) { + t.Errorf("unexpected error: %v", err) + } + + if !cmp.Equal(worklog, tc.worklog) { + t.Errorf("unexpected worklog structure: %s", cmp.Diff(worklog, tc.worklog)) + } + }) + } +} + +func TestIssueService_GetWatchers(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10002/watchers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/issue/10002/watchers") + + fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","accountId": "000000000000000000000000","displayName":"Fred F. User","active":false}]}`) + }) + + testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") + + fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","key":"fred","accountId": "000000000000000000000000", + "emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", + "24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", + "32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":true,"timeZone":"Australia/Sydney","groups":{"size":3,"items":[ + {"name":"jira-user","self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-user"},{"name":"jira-admin", + "self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-admin"},{"name":"important","self":"http://www.example.com/jira/rest/api/2/group?groupname=important" + }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) + }) + + watchers, _, err := testClient.Issue.GetWatchers("10002") + if err != nil { + t.Errorf("Error given: %s", err) + return + } + if watchers == nil { + t.Error("Expected watchers. Watchers is nil") + return + } + if len(*watchers) != 1 { + t.Errorf("Expected 1 watcher, got: %d", len(*watchers)) + return + } + if (*watchers)[0].AccountID != "000000000000000000000000" { + t.Error("Expected watcher accountId 000000000000000000000000") + } +} + +func TestIssueService_DeprecatedGetWatchers(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10002/watchers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/issue/10002/watchers") + + fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000", "accountId": "000000000000000000000000", "displayName":"Fred F. User","active":false}]}`) + }) + + testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") + + fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000", "accountId": "000000000000000000000000", "key": "", "name": "", "emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", + "24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", + "32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":true,"timeZone":"Australia/Sydney","groups":{"size":3,"items":[ + {"name":"jira-user","self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-user"},{"name":"jira-admin", + "self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-admin"},{"name":"important","self":"http://www.example.com/jira/rest/api/2/group?groupname=important" + }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) + }) + + watchers, _, err := testClient.Issue.GetWatchers("10002") + if err != nil { + t.Errorf("Error given: %s", err) + return + } + if watchers == nil { + t.Error("Expected watchers. Watchers is nil") + return + } + if len(*watchers) != 1 { + t.Errorf("Expected 1 watcher, got: %d", len(*watchers)) + return + } + if (*watchers)[0].AccountID != "000000000000000000000000" { + t.Error("Expected accountId 000000000000000000000000") + } +} + +func TestIssueService_UpdateAssignee(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10002/assignee", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testRequestURL(t, r, "/rest/api/2/issue/10002/assignee") + + w.WriteHeader(http.StatusNoContent) + }) + + resp, err := testClient.Issue.UpdateAssignee("10002", &User{ + Name: "test-username", + }) + + if resp.StatusCode != 204 { + t.Error("Expected issue not re-assigned.") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_Get_Fields_Changelog(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/issue/10002") + + fmt.Fprint(w, `{"expand":"changelog","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","changelog":{"startAt": 0,"maxResults": 1, "total": 1, "histories": [{"id": "10002", "author": {"self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "key": "fred", "emailAddress": "fred@example.com", "avatarUrls": {"48x48": "http://www.example.com/secure/useravatar?ownerId=fred&avatarId=33072", "24x24": "http://www.example.com/secure/useravatar?size=small&ownerId=fred&avatarId=33072", "16x16": "http://www.example.com/secure/useravatar?size=xsmall&ownerId=fred&avatarId=33072", "32x32": "http://www.example.com/secure/useravatar?size=medium&ownerId=fred&avatarId=33072"},"displayName":"Fred","active": true,"timeZone":"Australia/Sydney"},"created":"2018-06-20T16:50:35.000+0300","items":[{"field":"Rank","fieldtype":"custom","from":"","fromString":"","to":"","toString":"Ranked higher"}]}]}}`) + }) + + issue, _, _ := testClient.Issue.Get("10002", &GetQueryOptions{Expand: "changelog"}) + if issue == nil { + t.Error("Expected issue. Issue is nil") + return + } + + if len(issue.Changelog.Histories) != 1 { + t.Errorf("Expected one history item, %v found", len(issue.Changelog.Histories)) + } + + if issue.Changelog.Histories[0].Created != "2018-06-20T16:50:35.000+0300" { + t.Errorf("Expected created time of history item 2018-06-20T16:50:35.000+0300, %v got", issue.Changelog.Histories[0].Created) + } + + tm, _ := time.Parse("2006-01-02T15:04:05.999-0700", "2018-06-20T16:50:35.000+0300") + + if ct, _ := issue.Changelog.Histories[0].CreatedTime(); !tm.Equal(ct) { + t.Errorf("Expected CreatedTime func return %v time, %v got", tm, ct) + } +} + +func TestIssueService_Get_Transitions(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/issue/10002") + + fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/api/latest/issue/10002","key":"EX-1","transitions":[{"id":"121","name":"Start","to":{"self":"http://www.example.com/rest/api/2/status/10444","description":"","iconUrl":"http://www.example.com/images/icons/statuses/inprogress.png","name":"In progress","id":"10444","statusCategory":{"self":"http://www.example.com/rest/api/2/statuscategory/4","id":4,"key":"indeterminate","colorName":"yellow","name":"In Progress"}}}]}`) + }) + + issue, _, _ := testClient.Issue.Get("10002", &GetQueryOptions{Expand: "transitions"}) + if issue == nil { + t.Error("Expected issue. Issue is nil") + return + } + + if len(issue.Transitions) != 1 { + t.Errorf("Expected one transition item, %v found", len(issue.Transitions)) + } + + transition := issue.Transitions[0] + + if transition.Name != "Start" { + t.Errorf("Expected 'Start' transition to be available, got %q", transition.Name) + } + + if transition.To.Name != "In progress" { + t.Errorf("Expected transition to lead to status 'In progress', got %q", transition.To.Name) + } +} + +func TestIssueService_Get_Fields_AffectsVersions(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/issue/10002") + + fmt.Fprint(w, `{"fields":{"versions":[{"self":"http://www.example.com/jira/rest/api/2/version/10705","id":"10705","description":"test description","name":"2.1.0-rc3","archived":false,"released":false,"releaseDate":"2018-09-30"}]}}`) + }) + + issue, _, err := testClient.Issue.Get("10002", nil) + if err != nil { + t.Errorf("Error given: %s", err) + } + if issue == nil { + t.Error("Expected issue. Issue is nil") + return + } + if !reflect.DeepEqual(issue.Fields.AffectsVersions, []*AffectsVersion{ + { + ID: "10705", + Name: "2.1.0-rc3", + Self: "http://www.example.com/jira/rest/api/2/version/10705", + ReleaseDate: "2018-09-30", + Released: Bool(false), + Archived: Bool(false), + Description: "test description", + }, + }) { + t.Error("Expected AffectsVersions for the returned issue") + } +} + +func TestIssueService_GetRemoteLinks(t *testing.T) { + setup() + defer teardown() + + testAPIEndpoint := "/rest/api/2/issue/123/remotelink" + + raw, err := os.ReadFile("../testing/mock-data/remote_links.json") + if err != nil { + t.Error(err.Error()) + } + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + remoteLinks, _, err := testClient.Issue.GetRemoteLinks("123") + if err != nil { + t.Errorf("Got error: %v", err) + } + + if remoteLinks == nil { + t.Error("Expected remote links list. Got nil.") + return + } + + if len(*remoteLinks) != 2 { + t.Errorf("Expected 2 remote links. Got %d", len(*remoteLinks)) + } + + if !(*remoteLinks)[0].Object.Status.Resolved { + t.Errorf("First remote link object status should be resolved") + } +} + +func TestIssueService_AddRemoteLink(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10000/remotelink", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/issue/10000/remotelink") + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"id": 10000, "self": "https://your-domain.atlassian.net/rest/api/issue/MKY-1/remotelink/10000"}`) + }) + r := &RemoteLink{ + Application: &RemoteLinkApplication{ + Name: "My Acme Tracker", + Type: "com.acme.tracker", + }, + GlobalID: "system=http://www.mycompany.com/support&id=1", + Relationship: "causes", + Object: &RemoteLinkObject{ + Summary: "Customer support issue", + Icon: &RemoteLinkIcon{ + Url16x16: "http://www.mycompany.com/support/ticket.png", + Title: "Support Ticket", + }, + Title: "TSTSUP-111", + URL: "http://www.mycompany.com/support?id=1", + Status: &RemoteLinkStatus{ + Icon: &RemoteLinkIcon{ + Url16x16: "http://www.mycompany.com/support/resolved.png", + Title: "Case Closed", + Link: "http://www.mycompany.com/support?id=1&details=closed", + }, + Resolved: true, + }, + }, + } + record, _, err := testClient.Issue.AddRemoteLink("10000", r) + if record == nil { + t.Error("Expected Record. Record is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestIssueService_UpdateRemoteLink(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/100/remotelink/200", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testRequestURL(t, r, "/rest/api/2/issue/100/remotelink/200") + + w.WriteHeader(http.StatusNoContent) + }) + r := &RemoteLink{ + Application: &RemoteLinkApplication{ + Name: "My Acme Tracker", + Type: "com.acme.tracker", + }, + GlobalID: "system=http://www.mycompany.com/support&id=1", + Relationship: "causes", + Object: &RemoteLinkObject{ + Summary: "Customer support issue", + Icon: &RemoteLinkIcon{ + Url16x16: "http://www.mycompany.com/support/ticket.png", + Title: "Support Ticket", + }, + Title: "TSTSUP-111", + URL: "http://www.mycompany.com/support?id=1", + Status: &RemoteLinkStatus{ + Icon: &RemoteLinkIcon{ + Url16x16: "http://www.mycompany.com/support/resolved.png", + Title: "Case Closed", + Link: "http://www.mycompany.com/support?id=1&details=closed", + }, + Resolved: true, + }, + }, + } + _, err := testClient.Issue.UpdateRemoteLink("100", 200, r) + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestTime_MarshalJSON(t *testing.T) { + timeFormatParseFrom := "2006-01-02T15:04:05.999Z" + testCases := []struct { + name string + inputTime string + expected string + }{ + { + name: "test without ms", + inputTime: "2020-04-01T01:01:01.000Z", + expected: "\"2020-04-01T01:01:01.000+0000\"", + }, + { + name: "test with ms", + inputTime: "2020-04-01T01:01:01.001Z", + expected: "\"2020-04-01T01:01:01.001+0000\"", + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + rawTime, _ := time.Parse(timeFormatParseFrom, tt.inputTime) + time := Time(rawTime) + got, _ := time.MarshalJSON() + if string(got) != tt.expected { + t.Errorf("Time.MarshalJSON() = %v, want %v", string(got), tt.expected) + } + }) + } +} diff --git a/onpremise/issuelinktype.go b/onpremise/issuelinktype.go new file mode 100644 index 0000000..c8cf542 --- /dev/null +++ b/onpremise/issuelinktype.go @@ -0,0 +1,141 @@ +package onpremise + +import ( + "context" + "encoding/json" + "fmt" + "io" +) + +// IssueLinkTypeService handles issue link types for the Jira instance / API. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Issue-link-types +type IssueLinkTypeService struct { + client *Client +} + +// GetListWithContext gets all of the issue link types from Jira. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-get +func (s *IssueLinkTypeService) GetListWithContext(ctx context.Context) ([]IssueLinkType, *Response, error) { + apiEndpoint := "rest/api/2/issueLinkType" + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + linkTypeList := []IssueLinkType{} + resp, err := s.client.Do(req, &linkTypeList) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return linkTypeList, resp, nil +} + +// GetList wraps GetListWithContext using the background context. +func (s *IssueLinkTypeService) GetList() ([]IssueLinkType, *Response, error) { + return s.GetListWithContext(context.Background()) +} + +// GetWithContext gets info of a specific issue link type from Jira. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-get +func (s *IssueLinkTypeService) GetWithContext(ctx context.Context, ID string) (*IssueLinkType, *Response, error) { + apiEndPoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + if err != nil { + return nil, nil, err + } + + linkType := new(IssueLinkType) + resp, err := s.client.Do(req, linkType) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return linkType, resp, nil +} + +// Get wraps GetWithContext using the background context. +func (s *IssueLinkTypeService) Get(ID string) (*IssueLinkType, *Response, error) { + return s.GetWithContext(context.Background(), ID) +} + +// CreateWithContext creates an issue link type in Jira. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-post +func (s *IssueLinkTypeService) CreateWithContext(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { + apiEndpoint := "/rest/api/2/issueLinkType" + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, linkType) + if err != nil { + return nil, nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return nil, resp, err + } + + responseLinkType := new(IssueLinkType) + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + e := fmt.Errorf("could not read the returned data") + return nil, resp, NewJiraError(resp, e) + } + err = json.Unmarshal(data, responseLinkType) + if err != nil { + e := fmt.Errorf("could no unmarshal the data into struct") + return nil, resp, NewJiraError(resp, e) + } + return linkType, resp, nil +} + +// Create wraps CreateWithContext using the background context. +func (s *IssueLinkTypeService) Create(linkType *IssueLinkType) (*IssueLinkType, *Response, error) { + return s.CreateWithContext(context.Background(), linkType) +} + +// UpdateWithContext updates an issue link type. The issue is found by key. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-put +// Caller must close resp.Body +func (s *IssueLinkTypeService) UpdateWithContext(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", linkType.ID) + req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, linkType) + if err != nil { + return nil, nil, err + } + resp, err := s.client.Do(req, nil) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + ret := *linkType + return &ret, resp, nil +} + +// Update wraps UpdateWithContext using the background context. +// Caller must close resp.Body +func (s *IssueLinkTypeService) Update(linkType *IssueLinkType) (*IssueLinkType, *Response, error) { + return s.UpdateWithContext(context.Background(), linkType) +} + +// DeleteWithContext deletes an issue link type based on provided ID. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-delete +// Caller must close resp.Body +func (s *IssueLinkTypeService) DeleteWithContext(ctx context.Context, ID string) (*Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + return resp, err +} + +// Delete wraps DeleteWithContext using the background context. +// Caller must close resp.Body +func (s *IssueLinkTypeService) Delete(ID string) (*Response, error) { + return s.DeleteWithContext(context.Background(), ID) +} diff --git a/onpremise/issuelinktype_test.go b/onpremise/issuelinktype_test.go new file mode 100644 index 0000000..397bb9a --- /dev/null +++ b/onpremise/issuelinktype_test.go @@ -0,0 +1,118 @@ +package onpremise + +import ( + "fmt" + "net/http" + "os" + "testing" +) + +func TestIssueLinkTypeService_GetList(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/2/issueLinkType" + + raw, err := os.ReadFile("../testing/mock-data/all_issuelinktypes.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + linkTypes, _, err := testClient.IssueLinkType.GetList() + if linkTypes == nil { + t.Error("Expected issueLinkType list. LinkTypes is nil") + } + if err != nil { + t.Errorf("Error give: %s", err) + } +} + +func TestIssueLinkTypeService_Get(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issueLinkType/123", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/issueLinkType/123") + + fmt.Fprint(w, `{"id": "123","name": "Blocked","inward": "Blocked","outward": "Blocked", + "self": "https://www.example.com/jira/rest/api/2/issueLinkType/123"}`) + }) + + if linkType, _, err := testClient.IssueLinkType.Get("123"); err != nil { + t.Errorf("Error given: %s", err) + } else if linkType == nil { + t.Error("Expected linkType. LinkType is nil") + } +} + +func TestIssueLinkTypeService_Create(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issueLinkType", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/issueLinkType") + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"id":"10021","name":"Problem/Incident","inward":"is caused by", + "outward":"causes","self":"https://www.example.com/jira/rest/api/2/issueLinkType/10021"}`) + }) + + lt := &IssueLinkType{ + Name: "Problem/Incident", + Inward: "is caused by", + Outward: "causes", + } + + if linkType, _, err := testClient.IssueLinkType.Create(lt); err != nil { + t.Errorf("Error given: %s", err) + } else if linkType == nil { + t.Error("Expected linkType. LinkType is nil") + } +} + +func TestIssueLinkTypeService_Update(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issueLinkType/100", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testRequestURL(t, r, "/rest/api/2/issueLinkType/100") + + w.WriteHeader(http.StatusNoContent) + }) + + lt := &IssueLinkType{ + ID: "100", + Name: "Problem/Incident", + Inward: "is caused by", + Outward: "causes", + } + + if linkType, _, err := testClient.IssueLinkType.Update(lt); err != nil { + t.Errorf("Error given: %s", err) + } else if linkType == nil { + t.Error("Expected linkType. LinkType is nil") + } +} + +func TestIssueLinkTypeService_Delete(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issueLinkType/100", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, "/rest/api/2/issueLinkType/100") + + w.WriteHeader(http.StatusNoContent) + }) + + resp, err := testClient.IssueLinkType.Delete("100") + if resp.StatusCode != http.StatusNoContent { + t.Error("Expected issue not deleted.") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} diff --git a/onpremise/jira.go b/onpremise/jira.go new file mode 100644 index 0000000..456708d --- /dev/null +++ b/onpremise/jira.go @@ -0,0 +1,345 @@ +package onpremise + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "reflect" + "strings" + + "github.com/google/go-querystring/query" +) + +// httpClient defines an interface for an http.Client implementation so that alternative +// http Clients can be passed in for making requests +type httpClient interface { + Do(request *http.Request) (response *http.Response, err error) +} + +// A Client manages communication with the Jira API. +type Client struct { + // HTTP client used to communicate with the API. + client httpClient + + // Base URL for API requests. + baseURL *url.URL + + // Session storage if the user authenticates with a Session cookie + session *Session + + // Services used for talking to different parts of the Jira API. + Authentication *AuthenticationService + Issue *IssueService + Project *ProjectService + Board *BoardService + Sprint *SprintService + User *UserService + Group *GroupService + Version *VersionService + Priority *PriorityService + Field *FieldService + Component *ComponentService + Resolution *ResolutionService + StatusCategory *StatusCategoryService + Filter *FilterService + Role *RoleService + PermissionScheme *PermissionSchemeService + Status *StatusService + IssueLinkType *IssueLinkTypeService + Organization *OrganizationService + ServiceDesk *ServiceDeskService + Customer *CustomerService + Request *RequestService +} + +// NewClient returns a new Jira API client. +// If a nil httpClient is provided, http.DefaultClient will be used. +// To use API methods which require authentication you can follow the preferred solution and +// provide an http.Client that will perform the authentication for you with OAuth and HTTP Basic (such as that provided by the golang.org/x/oauth2 library). +// As an alternative you can use Session Cookie based authentication provided by this package as well. +// See https://docs.atlassian.com/jira/REST/latest/#authentication +// baseURL is the HTTP endpoint of your Jira instance and should always be specified with a trailing slash. +func NewClient(httpClient httpClient, baseURL string) (*Client, error) { + if httpClient == nil { + httpClient = http.DefaultClient + } + + // ensure the baseURL contains a trailing slash so that all paths are preserved in later calls + if !strings.HasSuffix(baseURL, "/") { + baseURL += "/" + } + + parsedBaseURL, err := url.Parse(baseURL) + if err != nil { + return nil, err + } + + c := &Client{ + client: httpClient, + baseURL: parsedBaseURL, + } + c.Authentication = &AuthenticationService{client: c} + c.Issue = &IssueService{client: c} + c.Project = &ProjectService{client: c} + c.Board = &BoardService{client: c} + c.Sprint = &SprintService{client: c} + c.User = &UserService{client: c} + c.Group = &GroupService{client: c} + c.Version = &VersionService{client: c} + c.Priority = &PriorityService{client: c} + c.Field = &FieldService{client: c} + c.Component = &ComponentService{client: c} + c.Resolution = &ResolutionService{client: c} + c.StatusCategory = &StatusCategoryService{client: c} + c.Filter = &FilterService{client: c} + c.Role = &RoleService{client: c} + c.PermissionScheme = &PermissionSchemeService{client: c} + c.Status = &StatusService{client: c} + c.IssueLinkType = &IssueLinkTypeService{client: c} + c.Organization = &OrganizationService{client: c} + c.ServiceDesk = &ServiceDeskService{client: c} + c.Customer = &CustomerService{client: c} + c.Request = &RequestService{client: c} + + return c, nil +} + +// NewRawRequestWithContext creates an API request. +// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client. +// Allows using an optional native io.Reader for sourcing the request body. +func (c *Client) NewRawRequestWithContext(ctx context.Context, method, urlStr string, body io.Reader) (*http.Request, error) { + rel, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + // Relative URLs should be specified without a preceding slash since baseURL will have the trailing slash + rel.Path = strings.TrimLeft(rel.Path, "/") + + u := c.baseURL.ResolveReference(rel) + + req, err := http.NewRequestWithContext(ctx, method, u.String(), body) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + + // Set authentication information + if c.Authentication.authType == authTypeSession { + // Set session cookie if there is one + if c.session != nil { + for _, cookie := range c.session.Cookies { + req.AddCookie(cookie) + } + } + } else if c.Authentication.authType == authTypeBasic { + // Set basic auth information + if c.Authentication.username != "" { + req.SetBasicAuth(c.Authentication.username, c.Authentication.password) + } + } + + return req, nil +} + +// NewRawRequest wraps NewRawRequestWithContext using the background context. +func (c *Client) NewRawRequest(method, urlStr string, body io.Reader) (*http.Request, error) { + return c.NewRawRequestWithContext(context.Background(), method, urlStr, body) +} + +// NewRequestWithContext creates an API request. +// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client. +// If specified, the value pointed to by body is JSON encoded and included as the request body. +func (c *Client) NewRequestWithContext(ctx context.Context, method, urlStr string, body interface{}) (*http.Request, error) { + rel, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + // Relative URLs should be specified without a preceding slash since baseURL will have the trailing slash + rel.Path = strings.TrimLeft(rel.Path, "/") + + u := c.baseURL.ResolveReference(rel) + + var buf io.ReadWriter + if body != nil { + buf = new(bytes.Buffer) + err = json.NewEncoder(buf).Encode(body) + if err != nil { + return nil, err + } + } + + req, err := http.NewRequestWithContext(ctx, method, u.String(), buf) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + + // Set authentication information + if c.Authentication.authType == authTypeSession { + // Set session cookie if there is one + if c.session != nil { + for _, cookie := range c.session.Cookies { + req.AddCookie(cookie) + } + } + } else if c.Authentication.authType == authTypeBasic { + // Set basic auth information + if c.Authentication.username != "" { + req.SetBasicAuth(c.Authentication.username, c.Authentication.password) + } + } + + return req, nil +} + +// NewRequest wraps NewRequestWithContext using the background context. +func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) { + return c.NewRequestWithContext(context.Background(), method, urlStr, body) +} + +// addOptions adds the parameters in opt as URL query parameters to s. opt +// must be a struct whose fields may contain "url" tags. +func addOptions(s string, opt interface{}) (string, error) { + v := reflect.ValueOf(opt) + if v.Kind() == reflect.Ptr && v.IsNil() { + return s, nil + } + + u, err := url.Parse(s) + if err != nil { + return s, err + } + + qs, err := query.Values(opt) + if err != nil { + return s, err + } + + u.RawQuery = qs.Encode() + return u.String(), nil +} + +// NewMultiPartRequestWithContext creates an API request including a multi-part file. +// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client. +// If specified, the value pointed to by buf is a multipart form. +func (c *Client) NewMultiPartRequestWithContext(ctx context.Context, method, urlStr string, buf *bytes.Buffer) (*http.Request, error) { + rel, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + // Relative URLs should be specified without a preceding slash since baseURL will have the trailing slash + rel.Path = strings.TrimLeft(rel.Path, "/") + + u := c.baseURL.ResolveReference(rel) + + req, err := http.NewRequestWithContext(ctx, method, u.String(), buf) + if err != nil { + return nil, err + } + + // Set required headers + req.Header.Set("X-Atlassian-Token", "nocheck") + + // Set authentication information + if c.Authentication.authType == authTypeSession { + // Set session cookie if there is one + if c.session != nil { + for _, cookie := range c.session.Cookies { + req.AddCookie(cookie) + } + } + } else if c.Authentication.authType == authTypeBasic { + // Set basic auth information + if c.Authentication.username != "" { + req.SetBasicAuth(c.Authentication.username, c.Authentication.password) + } + } + + return req, nil +} + +// NewMultiPartRequest wraps NewMultiPartRequestWithContext using the background context. +func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (*http.Request, error) { + return c.NewMultiPartRequestWithContext(context.Background(), method, urlStr, buf) +} + +// Do sends an API request and returns the API response. +// The API response is JSON decoded and stored in the value pointed to by v, or returned as an error if an API error has occurred. +func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { + httpResp, err := c.client.Do(req) + if err != nil { + return nil, err + } + + err = CheckResponse(httpResp) + if err != nil { + // Even though there was an error, we still return the response + // in case the caller wants to inspect it further + return newResponse(httpResp, nil), err + } + + if v != nil { + // Open a NewDecoder and defer closing the reader only if there is a provided interface to decode to + defer httpResp.Body.Close() + err = json.NewDecoder(httpResp.Body).Decode(v) + } + + resp := newResponse(httpResp, v) + return resp, err +} + +// CheckResponse checks the API response for errors, and returns them if present. +// A response is considered an error if it has a status code outside the 200 range. +// The caller is responsible to analyze the response body. +// The body can contain JSON (if the error is intended) or xml (sometimes Jira just failes). +func CheckResponse(r *http.Response) error { + if c := r.StatusCode; 200 <= c && c <= 299 { + return nil + } + + err := fmt.Errorf("request failed. Please analyze the request body for more details. Status code: %d", r.StatusCode) + return err +} + +// GetBaseURL will return you the Base URL. +// This is the same URL as in the NewClient constructor +func (c *Client) GetBaseURL() url.URL { + return *c.baseURL +} + +// Response represents Jira API response. It wraps http.Response returned from +// API and provides information about paging. +type Response struct { + *http.Response + + StartAt int + MaxResults int + Total int +} + +func newResponse(r *http.Response, v interface{}) *Response { + resp := &Response{Response: r} + resp.populatePageValues(v) + return resp +} + +// Sets paging values if response json was parsed to searchResult type +// (can be extended with other types if they also need paging info) +func (r *Response) populatePageValues(v interface{}) { + switch value := v.(type) { + case *searchResult: + r.StartAt = value.StartAt + r.MaxResults = value.MaxResults + r.Total = value.Total + case *groupMembersResult: + r.StartAt = value.StartAt + r.MaxResults = value.MaxResults + r.Total = value.Total + } +} diff --git a/onpremise/jira_test.go b/onpremise/jira_test.go new file mode 100644 index 0000000..f9a5f6e --- /dev/null +++ b/onpremise/jira_test.go @@ -0,0 +1,452 @@ +package onpremise + +import ( + "bytes" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "strings" + "testing" + "time" +) + +const ( + testJiraInstanceURL = "https://issues.apache.org/jira/" +) + +var ( + // testMux is the HTTP request multiplexer used with the test server. + testMux *http.ServeMux + + // testClient is the Jira client being tested. + testClient *Client + + // testServer is a test HTTP server used to provide mock API responses. + testServer *httptest.Server +) + +// setup sets up a test HTTP server along with a jira.Client that is configured to talk to that test server. +// Tests should register handlers on mux which provide mock responses for the API method being tested. +func setup() { + // Test server + testMux = http.NewServeMux() + testServer = httptest.NewServer(testMux) + + // jira client configured to use test server + testClient, _ = NewClient(nil, testServer.URL) +} + +// teardown closes the test HTTP server. +func teardown() { + testServer.Close() +} + +func testMethod(t *testing.T, r *http.Request, want string) { + if got := r.Method; got != want { + t.Errorf("Request method: %v, want %v", got, want) + } +} + +func testRequestURL(t *testing.T, r *http.Request, want string) { + if got := r.URL.String(); !strings.HasPrefix(got, want) { + t.Errorf("Request URL: %v, want %v", got, want) + } +} + +func testRequestParams(t *testing.T, r *http.Request, want map[string]string) { + params := r.URL.Query() + + if len(params) != len(want) { + t.Errorf("Request params: %d, want %d", len(params), len(want)) + } + + for key, val := range want { + if got := params.Get(key); val != got { + t.Errorf("Request params: %s, want %s", got, val) + } + + } + +} + +func TestNewClient_WrongUrl(t *testing.T) { + c, err := NewClient(nil, "://issues.apache.org/jira/") + + if err == nil { + t.Error("Expected an error. Got none") + } + if c != nil { + t.Errorf("Expected no client. Got %+v", c) + } +} + +func TestNewClient_WithHttpClient(t *testing.T) { + httpClient := http.DefaultClient + httpClient.Timeout = 10 * time.Minute + + c, err := NewClient(httpClient, testJiraInstanceURL) + if err != nil { + t.Errorf("Got an error: %s", err) + } + if c == nil { + t.Error("Expected a client. Got none") + return + } + if !reflect.DeepEqual(c.client, httpClient) { + t.Errorf("HTTP clients are not equal. Injected %+v, got %+v", httpClient, c.client) + } +} + +func TestNewClient_WithServices(t *testing.T) { + c, err := NewClient(nil, testJiraInstanceURL) + + if err != nil { + t.Errorf("Got an error: %s", err) + } + if c.Authentication == nil { + t.Error("No AuthenticationService provided") + } + if c.Issue == nil { + t.Error("No IssueService provided") + } + if c.Project == nil { + t.Error("No ProjectService provided") + } + if c.Board == nil { + t.Error("No BoardService provided") + } + if c.Sprint == nil { + t.Error("No SprintService provided") + } + if c.User == nil { + t.Error("No UserService provided") + } + if c.Group == nil { + t.Error("No GroupService provided") + } + if c.Version == nil { + t.Error("No VersionService provided") + } + if c.Priority == nil { + t.Error("No PriorityService provided") + } + if c.Resolution == nil { + t.Error("No ResolutionService provided") + } + if c.StatusCategory == nil { + t.Error("No StatusCategoryService provided") + } +} + +func TestCheckResponse(t *testing.T) { + codes := []int{ + http.StatusOK, http.StatusPartialContent, 299, + } + + for _, c := range codes { + r := &http.Response{ + StatusCode: c, + } + if err := CheckResponse(r); err != nil { + t.Errorf("CheckResponse throws an error: %s", err) + } + } +} + +func TestClient_NewRequest(t *testing.T) { + c, err := NewClient(nil, testJiraInstanceURL) + if err != nil { + t.Errorf("An error occurred. Expected nil. Got %+v.", err) + } + + inURL, outURL := "rest/api/2/issue/", testJiraInstanceURL+"rest/api/2/issue/" + inBody, outBody := &Issue{Key: "MESOS"}, `{"key":"MESOS"}`+"\n" + req, _ := c.NewRequest("GET", inURL, inBody) + + // Test that relative URL was expanded + if got, want := req.URL.String(), outURL; got != want { + t.Errorf("NewRequest(%q) URL is %v, want %v", inURL, got, want) + } + + // Test that body was JSON encoded + body, _ := io.ReadAll(req.Body) + if got, want := string(body), outBody; got != want { + t.Errorf("NewRequest(%v) Body is %v, want %v", inBody, got, want) + } +} + +func TestClient_NewRawRequest(t *testing.T) { + c, err := NewClient(nil, testJiraInstanceURL) + if err != nil { + t.Errorf("An error occurred. Expected nil. Got %+v.", err) + } + + inURL, outURL := "rest/api/2/issue/", testJiraInstanceURL+"rest/api/2/issue/" + + outBody := `{"key":"MESOS"}` + "\n" + inBody := outBody + req, _ := c.NewRawRequest("GET", inURL, strings.NewReader(outBody)) + + // Test that relative URL was expanded + if got, want := req.URL.String(), outURL; got != want { + t.Errorf("NewRawRequest(%q) URL is %v, want %v", inURL, got, want) + } + + // Test that body was JSON encoded + body, _ := io.ReadAll(req.Body) + if got, want := string(body), outBody; got != want { + t.Errorf("NewRawRequest(%v) Body is %v, want %v", inBody, got, want) + } +} + +func testURLParseError(t *testing.T, err error) { + if err == nil { + t.Errorf("Expected error to be returned") + } + if err, ok := err.(*url.Error); !ok || err.Op != "parse" { + t.Errorf("Expected URL parse error, got %+v", err) + } +} + +func TestClient_NewRequest_BadURL(t *testing.T) { + c, err := NewClient(nil, testJiraInstanceURL) + if err != nil { + t.Errorf("An error occurred. Expected nil. Got %+v.", err) + } + _, err = c.NewRequest("GET", ":", nil) + testURLParseError(t, err) +} + +func TestClient_NewRequest_SessionCookies(t *testing.T) { + c, err := NewClient(nil, testJiraInstanceURL) + if err != nil { + t.Errorf("An error occurred. Expected nil. Got %+v.", err) + } + + cookie := &http.Cookie{Name: "testcookie", Value: "testvalue"} + c.session = &Session{Cookies: []*http.Cookie{cookie}} + c.Authentication.authType = authTypeSession + + inURL := "rest/api/2/issue/" + inBody := &Issue{Key: "MESOS"} + req, err := c.NewRequest("GET", inURL, inBody) + + if err != nil { + t.Errorf("An error occurred. Expected nil. Got %+v.", err) + } + + if len(req.Cookies()) != len(c.session.Cookies) { + t.Errorf("An error occurred. Expected %d cookie(s). Got %d.", len(c.session.Cookies), len(req.Cookies())) + } + + for i, v := range req.Cookies() { + if v.String() != c.session.Cookies[i].String() { + t.Errorf("An error occurred. Unexpected cookie. Expected %s, actual %s.", v.String(), c.session.Cookies[i].String()) + } + } +} + +func TestClient_NewRequest_BasicAuth(t *testing.T) { + c, err := NewClient(nil, testJiraInstanceURL) + if err != nil { + t.Errorf("An error occurred. Expected nil. Got %+v.", err) + } + + c.Authentication.SetBasicAuth("test-user", "test-password") + + inURL := "rest/api/2/issue/" + inBody := &Issue{Key: "MESOS"} + req, err := c.NewRequest("GET", inURL, inBody) + + if err != nil { + t.Errorf("An error occurred. Expected nil. Got %+v.", err) + } + + username, password, ok := req.BasicAuth() + if !ok || username != "test-user" || password != "test-password" { + t.Errorf("An error occurred. Expected basic auth username %s and password %s. Got username %s and password %s.", "test-user", "test-password", username, password) + } +} + +// If a nil body is passed to jira.NewRequest, make sure that nil is also passed to http.NewRequest. +// In most cases, passing an io.Reader that returns no content is fine, +// since there is no difference between an HTTP request body that is an empty string versus one that is not set at all. +// However in certain cases, intermediate systems may treat these differently resulting in subtle errors. +func TestClient_NewRequest_EmptyBody(t *testing.T) { + c, err := NewClient(nil, testJiraInstanceURL) + if err != nil { + t.Errorf("An error occurred. Expected nil. Got %+v.", err) + } + req, err := c.NewRequest("GET", "/", nil) + if err != nil { + t.Fatalf("NewRequest returned unexpected error: %v", err) + } + if req.Body != nil { + t.Fatalf("constructed request contains a non-nil Body") + } +} + +func TestClient_NewMultiPartRequest(t *testing.T) { + c, err := NewClient(nil, testJiraInstanceURL) + if err != nil { + t.Errorf("An error occurred. Expected nil. Got %+v.", err) + } + + cookie := &http.Cookie{Name: "testcookie", Value: "testvalue"} + c.session = &Session{Cookies: []*http.Cookie{cookie}} + c.Authentication.authType = authTypeSession + + inURL := "rest/api/2/issue/" + inBuf := bytes.NewBufferString("teststring") + req, err := c.NewMultiPartRequest("GET", inURL, inBuf) + + if err != nil { + t.Errorf("An error occurred. Expected nil. Got %+v.", err) + } + + if len(req.Cookies()) != len(c.session.Cookies) { + t.Errorf("An error occurred. Expected %d cookie(s). Got %d.", len(c.session.Cookies), len(req.Cookies())) + } + + for i, v := range req.Cookies() { + if v.String() != c.session.Cookies[i].String() { + t.Errorf("An error occurred. Unexpected cookie. Expected %s, actual %s.", v.String(), c.session.Cookies[i].String()) + } + } + + if req.Header.Get("X-Atlassian-Token") != "nocheck" { + t.Errorf("An error occurred. Unexpected X-Atlassian-Token header value. Expected nocheck, actual %s.", req.Header.Get("X-Atlassian-Token")) + } +} + +func TestClient_NewMultiPartRequest_BasicAuth(t *testing.T) { + c, err := NewClient(nil, testJiraInstanceURL) + if err != nil { + t.Errorf("An error occurred. Expected nil. Got %+v.", err) + } + + c.Authentication.SetBasicAuth("test-user", "test-password") + + inURL := "rest/api/2/issue/" + inBuf := bytes.NewBufferString("teststring") + req, err := c.NewMultiPartRequest("GET", inURL, inBuf) + + if err != nil { + t.Errorf("An error occurred. Expected nil. Got %+v.", err) + } + + username, password, ok := req.BasicAuth() + if !ok || username != "test-user" || password != "test-password" { + t.Errorf("An error occurred. Expected basic auth username %s and password %s. Got username %s and password %s.", "test-user", "test-password", username, password) + } + + if req.Header.Get("X-Atlassian-Token") != "nocheck" { + t.Errorf("An error occurred. Unexpected X-Atlassian-Token header value. Expected nocheck, actual %s.", req.Header.Get("X-Atlassian-Token")) + } +} + +func TestClient_Do(t *testing.T) { + setup() + defer teardown() + + type foo struct { + A string + } + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if m := "GET"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + fmt.Fprint(w, `{"A":"a"}`) + }) + + req, _ := testClient.NewRequest("GET", "/", nil) + body := new(foo) + testClient.Do(req, body) + + want := &foo{"a"} + if !reflect.DeepEqual(body, want) { + t.Errorf("Response body = %v, want %v", body, want) + } +} + +func TestClient_Do_HTTPResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if m := "GET"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + fmt.Fprint(w, `{"A":"a"}`) + }) + + req, _ := testClient.NewRequest("GET", "/", nil) + res, _ := testClient.Do(req, nil) + _, err := io.ReadAll(res.Body) + + if err != nil { + t.Errorf("Error on parsing HTTP Response = %v", err.Error()) + } else if res.StatusCode != 200 { + t.Errorf("Response code = %v, want %v", res.StatusCode, 200) + } +} + +func TestClient_Do_HTTPError(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Bad Request", 400) + }) + + req, _ := testClient.NewRequest("GET", "/", nil) + _, err := testClient.Do(req, nil) + + if err == nil { + t.Error("Expected HTTP 400 error.") + } +} + +// Test handling of an error caused by the internal http client's Do() function. +// A redirect loop is pretty unlikely to occur within the Jira API, but does allow us to exercise the right code path. +func TestClient_Do_RedirectLoop(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/", http.StatusFound) + }) + + req, _ := testClient.NewRequest("GET", "/", nil) + _, err := testClient.Do(req, nil) + + if err == nil { + t.Error("Expected error to be returned.") + } + if err, ok := err.(*url.Error); !ok { + t.Errorf("Expected a URL error; got %+v.", err) + } +} + +func TestClient_GetBaseURL_WithURL(t *testing.T) { + u, err := url.Parse(testJiraInstanceURL) + if err != nil { + t.Errorf("URL parsing -> Got an error: %s", err) + } + + c, err := NewClient(nil, testJiraInstanceURL) + if err != nil { + t.Errorf("Client creation -> Got an error: %s", err) + } + if c == nil { + t.Error("Expected a client. Got none") + } + + if b := c.GetBaseURL(); !reflect.DeepEqual(b, *u) { + t.Errorf("Base URLs are not equal. Expected %+v, got %+v", *u, b) + } +} diff --git a/onpremise/metaissue.go b/onpremise/metaissue.go new file mode 100644 index 0000000..6fff176 --- /dev/null +++ b/onpremise/metaissue.go @@ -0,0 +1,236 @@ +package onpremise + +import ( + "context" + "fmt" + "strings" + + "github.com/google/go-querystring/query" + "github.com/trivago/tgo/tcontainer" +) + +// CreateMetaInfo contains information about fields and their attributed to create a ticket. +type CreateMetaInfo struct { + Expand string `json:"expand,omitempty"` + Projects []*MetaProject `json:"projects,omitempty"` +} + +// EditMetaInfo contains information about fields and their attributed to edit a ticket. +type EditMetaInfo struct { + Fields tcontainer.MarshalMap `json:"fields,omitempty"` +} + +// MetaProject is the meta information about a project returned from createmeta api +type MetaProject struct { + Expand string `json:"expand,omitempty"` + Self string `json:"self,omitempty"` + Id string `json:"id,omitempty"` + Key string `json:"key,omitempty"` + Name string `json:"name,omitempty"` + // omitted avatarUrls + IssueTypes []*MetaIssueType `json:"issuetypes,omitempty"` +} + +// MetaIssueType represents the different issue types a project has. +// +// Note: Fields is interface because this is an object which can +// have arbitraty keys related to customfields. It is not possible to +// expect these for a general way. This will be returning a map. +// Further processing must be done depending on what is required. +type MetaIssueType struct { + Self string `json:"self,omitempty"` + Id string `json:"id,omitempty"` + Description string `json:"description,omitempty"` + IconUrl string `json:"iconurl,omitempty"` + Name string `json:"name,omitempty"` + Subtasks bool `json:"subtask,omitempty"` + Expand string `json:"expand,omitempty"` + Fields tcontainer.MarshalMap `json:"fields,omitempty"` +} + +// GetCreateMetaWithContext makes the api call to get the meta information required to create a ticket +func (s *IssueService) GetCreateMetaWithContext(ctx context.Context, projectkeys string) (*CreateMetaInfo, *Response, error) { + return s.GetCreateMetaWithOptionsWithContext(ctx, &GetQueryOptions{ProjectKeys: projectkeys, Expand: "projects.issuetypes.fields"}) +} + +// GetCreateMeta wraps GetCreateMetaWithContext using the background context. +func (s *IssueService) GetCreateMeta(projectkeys string) (*CreateMetaInfo, *Response, error) { + return s.GetCreateMetaWithContext(context.Background(), projectkeys) +} + +// GetCreateMetaWithOptionsWithContext makes the api call to get the meta information without requiring to have a projectKey +func (s *IssueService) GetCreateMetaWithOptionsWithContext(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { + apiEndpoint := "rest/api/2/issue/createmeta" + + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + if options != nil { + q, err := query.Values(options) + if err != nil { + return nil, nil, err + } + req.URL.RawQuery = q.Encode() + } + + meta := new(CreateMetaInfo) + resp, err := s.client.Do(req, meta) + + if err != nil { + return nil, resp, err + } + + return meta, resp, nil +} + +// GetCreateMetaWithOptions wraps GetCreateMetaWithOptionsWithContext using the background context. +func (s *IssueService) GetCreateMetaWithOptions(options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { + return s.GetCreateMetaWithOptionsWithContext(context.Background(), options) +} + +// GetEditMetaWithContext makes the api call to get the edit meta information for an issue +func (s *IssueService) GetEditMetaWithContext(ctx context.Context, issue *Issue) (*EditMetaInfo, *Response, error) { + apiEndpoint := fmt.Sprintf("/rest/api/2/issue/%s/editmeta", issue.Key) + + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + meta := new(EditMetaInfo) + resp, err := s.client.Do(req, meta) + + if err != nil { + return nil, resp, err + } + + return meta, resp, nil +} + +// GetEditMeta wraps GetEditMetaWithContext using the background context. +func (s *IssueService) GetEditMeta(issue *Issue) (*EditMetaInfo, *Response, error) { + return s.GetEditMetaWithContext(context.Background(), issue) +} + +// GetProjectWithName returns a project with "name" from the meta information received. If not found, this returns nil. +// The comparison of the name is case insensitive. +func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject { + for _, m := range m.Projects { + if strings.EqualFold(m.Name, name) { + return m + } + } + return nil +} + +// GetProjectWithKey returns a project with "name" from the meta information received. If not found, this returns nil. +// The comparison of the name is case insensitive. +func (m *CreateMetaInfo) GetProjectWithKey(key string) *MetaProject { + for _, m := range m.Projects { + if strings.EqualFold(m.Key, key) { + return m + } + } + return nil +} + +// GetIssueTypeWithName returns an IssueType with name from a given MetaProject. If not found, this returns nil. +// The comparison of the name is case insensitive +func (p *MetaProject) GetIssueTypeWithName(name string) *MetaIssueType { + for _, m := range p.IssueTypes { + if strings.EqualFold(m.Name, name) { + return m + } + } + return nil +} + +// GetMandatoryFields returns a map of all the required fields from the MetaIssueTypes. +// if a field returned by the api was: +// +// "customfield_10806": { +// "required": true, +// "schema": { +// "type": "any", +// "custom": "com.pyxis.greenhopper.jira:gh-epic-link", +// "customId": 10806 +// }, +// "name": "Epic Link", +// "hasDefaultValue": false, +// "operations": [ +// "set" +// ] +// } +// +// the returned map would have "Epic Link" as the key and "customfield_10806" as value. +// This choice has been made so that the it is easier to generate the create api request later. +func (t *MetaIssueType) GetMandatoryFields() (map[string]string, error) { + ret := make(map[string]string) + for key := range t.Fields { + required, err := t.Fields.Bool(key + "/required") + if err != nil { + return nil, err + } + if required { + name, err := t.Fields.String(key + "/name") + if err != nil { + return nil, err + } + ret[name] = key + } + } + return ret, nil +} + +// GetAllFields returns a map of all the fields for an IssueType. This includes all required and not required. +// The key of the returned map is what you see in the form and the value is how it is representated in the jira schema. +func (t *MetaIssueType) GetAllFields() (map[string]string, error) { + ret := make(map[string]string) + for key := range t.Fields { + + name, err := t.Fields.String(key + "/name") + if err != nil { + return nil, err + } + ret[name] = key + } + return ret, nil +} + +// CheckCompleteAndAvailable checks if the given fields satisfies the mandatory field required to create a issue for the given type +// And also if the given fields are available. +func (t *MetaIssueType) CheckCompleteAndAvailable(config map[string]string) (bool, error) { + mandatory, err := t.GetMandatoryFields() + if err != nil { + return false, err + } + all, err := t.GetAllFields() + if err != nil { + return false, err + } + + // check templateconfig against mandatory fields + for key := range mandatory { + if _, okay := config[key]; !okay { + var requiredFields []string + for name := range mandatory { + requiredFields = append(requiredFields, name) + } + return false, fmt.Errorf("required field not found in provided jira.fields. Required are: %#v", requiredFields) + } + } + + // check templateConfig against all fields to verify they are available + for key := range config { + if _, okay := all[key]; !okay { + var availableFields []string + for name := range all { + availableFields = append(availableFields, name) + } + return false, fmt.Errorf("fields in jira.fields are not available in jira. Available are: %#v", availableFields) + } + } + + return true, nil +} diff --git a/onpremise/metaissue_test.go b/onpremise/metaissue_test.go new file mode 100644 index 0000000..6c6e5fb --- /dev/null +++ b/onpremise/metaissue_test.go @@ -0,0 +1,1078 @@ +package onpremise + +import ( + "fmt" + "net/http" + "net/url" + "testing" +) + +func TestIssueService_GetCreateMeta_Success(t *testing.T) { + setup() + defer teardown() + + testAPIEndpoint := "/rest/api/2/issue/createmeta" + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + + fmt.Fprint(w, `{ + "expand": "projects", + "projects": [{ + "expand": "issuetypes", + "self": "https://my.jira.com/rest/api/2/project/11300", + "id": "11300", + "key": "SPN", + "name": "Super Project Name", + "avatarUrls": { + "48x48": "https://my.jira.com/secure/projectavatar?pid=11300&avatarId=14405", + "24x24": "https://my.jira.com/secure/projectavatar?size=small&pid=11300&avatarId=14405", + "16x16": "https://my.jira.com/secure/projectavatar?size=xsmall&pid=11300&avatarId=14405", + "32x32": "https://my.jira.com/secure/projectavatar?size=medium&pid=11300&avatarId=14405" + }, + "issuetypes": [{ + "self": "https://my.jira.com/rest/api/2/issuetype/6", + "id": "6", + "description": "An issue which ideally should be able to be completed in one step", + "iconUrl": "https://my.jira.com/secure/viewavatar?size=xsmall&avatarId=14006&avatarType=issuetype", + "name": "Request", + "subtask": false, + "expand": "fields", + "fields": { + "summary": { + "required": true, + "schema": { + "type": "string", + "system": "summary" + }, + "name": "Summary", + "hasDefaultValue": false, + "operations": [ + "set" + ] + }, + "issuetype": { + "required": true, + "schema": { + "type": "issuetype", + "system": "issuetype" + }, + "name": "Issue Type", + "hasDefaultValue": false, + "operations": [ + + ], + "allowedValues": [{ + "self": "https://my.jira.com/rest/api/2/issuetype/6", + "id": "6", + "description": "An issue which ideally should be able to be completed in one step", + "iconUrl": "https://my.jira.com/secure/viewavatar?size=xsmall&avatarId=14006&avatarType=issuetype", + "name": "Request", + "subtask": false, + "avatarId": 14006 + }] + }, + "components": { + "required": true, + "schema": { + "type": "array", + "items": "component", + "system": "components" + }, + "name": "Component/s", + "hasDefaultValue": false, + "operations": [ + "add", + "set", + "remove" + ], + "allowedValues": [{ + "self": "https://my.jira.com/rest/api/2/component/14144", + "id": "14144", + "name": "Build automation", + "description": "Jenkins, webhooks, etc." + }, { + "self": "https://my.jira.com/rest/api/2/component/14149", + "id": "14149", + "name": "Caches and noSQL", + "description": "Cassandra, Memcached, Redis, Twemproxy, Xcache" + }, { + "self": "https://my.jira.com/rest/api/2/component/14152", + "id": "14152", + "name": "Cloud services", + "description": "AWS and similar services" + }, { + "self": "https://my.jira.com/rest/api/2/component/14147", + "id": "14147", + "name": "Code quality tools", + "description": "Code sniffer, Sonar" + }, { + "self": "https://my.jira.com/rest/api/2/component/14156", + "id": "14156", + "name": "Configuration management and provisioning", + "description": "Apache/PHP modules, Consul, Salt" + }, { + "self": "https://my.jira.com/rest/api/2/component/13606", + "id": "13606", + "name": "Cronjobs", + "description": "Cronjobs in general" + }, { + "self": "https://my.jira.com/rest/api/2/component/14150", + "id": "14150", + "name": "Data pipelines and queues", + "description": "Kafka, RabbitMq" + }, { + "self": "https://my.jira.com/rest/api/2/component/14159", + "id": "14159", + "name": "Database", + "description": "MySQL related problems" + }, { + "self": "https://my.jira.com/rest/api/2/component/14314", + "id": "14314", + "name": "Documentation" + }, { + "self": "https://my.jira.com/rest/api/2/component/14151", + "id": "14151", + "name": "Git", + "description": "Bitbucket, GitHub, GitLab, Git in general" + }, { + "self": "https://my.jira.com/rest/api/2/component/14155", + "id": "14155", + "name": "HTTP services", + "description": "CDN, HaProxy, HTTP, Varnish" + }, { + "self": "https://my.jira.com/rest/api/2/component/14154", + "id": "14154", + "name": "Job and service scheduling", + "description": "Chronos, Docker, Marathon, Mesos" + }, { + "self": "https://my.jira.com/rest/api/2/component/14158", + "id": "14158", + "name": "Legacy", + "description": "Everything related to legacy" + }, { + "self": "https://my.jira.com/rest/api/2/component/14157", + "id": "14157", + "name": "Monitoring", + "description": "Collectd, Nagios, Monitoring in general" + }, { + "self": "https://my.jira.com/rest/api/2/component/14148", + "id": "14148", + "name": "Other services" + }, { + "self": "https://my.jira.com/rest/api/2/component/13602", + "id": "13602", + "name": "Package management", + "description": "Composer, Medusa, Satis" + }, { + "self": "https://my.jira.com/rest/api/2/component/14145", + "id": "14145", + "name": "Release", + "description": "Directory config, release queries, rewrite rules" + }, { + "self": "https://my.jira.com/rest/api/2/component/14146", + "id": "14146", + "name": "Staging systems and VMs", + "description": "Stage, QA machines, KVMs,Vagrant" + }, { + "self": "https://my.jira.com/rest/api/2/component/14153", + "id": "14153", + "name": "Blog" + }, { + "self": "https://my.jira.com/rest/api/2/component/14143", + "id": "14143", + "name": "Test automation", + "description": "Testing infrastructure in general" + }, { + "self": "https://my.jira.com/rest/api/2/component/14221", + "id": "14221", + "name": "Internal Infrastructure" + }] + }, + "attachment": { + "required": false, + "schema": { + "type": "array", + "items": "attachment", + "system": "attachment" + }, + "name": "Attachment", + "hasDefaultValue": false, + "operations": [ + + ] + }, + "duedate": { + "required": false, + "schema": { + "type": "date", + "system": "duedate" + }, + "name": "Due Date", + "hasDefaultValue": false, + "operations": [ + "set" + ] + }, + "description": { + "required": false, + "schema": { + "type": "string", + "system": "description" + }, + "name": "Description", + "hasDefaultValue": false, + "operations": [ + "set" + ] + }, + "customfield_10806": { + "required": false, + "schema": { + "type": "any", + "custom": "com.pyxis.greenhopper.jira:gh-epic-link", + "customId": 10806 + }, + "name": "Epic Link", + "hasDefaultValue": false, + "operations": [ + "set" + ] + }, + "project": { + "required": true, + "schema": { + "type": "project", + "system": "project" + }, + "name": "Project", + "hasDefaultValue": false, + "operations": [ + "set" + ], + "allowedValues": [{ + "self": "https://my.jira.com/rest/api/2/project/11300", + "id": "11300", + "key": "SPN", + "name": "Super Project Name", + "avatarUrls": { + "48x48": "https://my.jira.com/secure/projectavatar?pid=11300&avatarId=14405", + "24x24": "https://my.jira.com/secure/projectavatar?size=small&pid=11300&avatarId=14405", + "16x16": "https://my.jira.com/secure/projectavatar?size=xsmall&pid=11300&avatarId=14405", + "32x32": "https://my.jira.com/secure/projectavatar?size=medium&pid=11300&avatarId=14405" + }, + "projectCategory": { + "self": "https://my.jira.com/rest/api/2/projectCategory/10100", + "id": "10100", + "description": "", + "name": "Product & Development" + } + }] + }, + "assignee": { + "required": true, + "schema": { + "type": "user", + "system": "assignee" + }, + "name": "Assignee", + "autoCompleteUrl": "https://my.jira.com/rest/api/latest/user/assignable/search?issueKey=null&username=", + "hasDefaultValue": true, + "operations": [ + "set" + ] + }, + "priority": { + "required": false, + "schema": { + "type": "priority", + "system": "priority" + }, + "name": "Priority", + "hasDefaultValue": true, + "operations": [ + "set" + ], + "allowedValues": [{ + "self": "https://my.jira.com/rest/api/2/priority/1", + "iconUrl": "https://my.jira.com/images/icons/priorities/blocker.svg", + "name": "Immediate", + "id": "1" + }, { + "self": "https://my.jira.com/rest/api/2/priority/2", + "iconUrl": "https://my.jira.com/images/icons/priorities/critical.svg", + "name": "Urgent", + "id": "2" + }, { + "self": "https://my.jira.com/rest/api/2/priority/3", + "iconUrl": "https://my.jira.com/images/icons/priorities/major.svg", + "name": "High", + "id": "3" + }, { + "self": "https://my.jira.com/rest/api/2/priority/6", + "iconUrl": "https://my.jira.com/images/icons/priorities/moderate.svg", + "name": "Moderate", + "id": "6" + }, { + "self": "https://my.jira.com/rest/api/2/priority/4", + "iconUrl": "https://my.jira.com/images/icons/priorities/minor.svg", + "name": "Normal", + "id": "4" + }, { + "self": "https://my.jira.com/rest/api/2/priority/5", + "iconUrl": "https://my.jira.com/images/icons/priorities/trivial.svg", + "name": "Low", + "id": "5" + }] + }, + "labels": { + "required": false, + "schema": { + "type": "array", + "items": "string", + "system": "labels" + }, + "name": "Labels", + "autoCompleteUrl": "https://my.jira.com/rest/api/1.0/labels/suggest?query=", + "hasDefaultValue": false, + "operations": [ + "add", + "set", + "remove" + ] + } + } + }] + }] + }`) + }) + + issue, _, err := testClient.Issue.GetCreateMeta("SPN") + if err != nil { + t.Errorf("Expected nil error but got %s", err) + } + + if len(issue.Projects) != 1 { + t.Errorf("Expected 1 project, got %d", len(issue.Projects)) + } + for _, project := range issue.Projects { + if len(project.IssueTypes) != 1 { + t.Errorf("Expected 1 issueTypes, got %d", len(project.IssueTypes)) + } + for _, issueTypes := range project.IssueTypes { + requiredFields := 0 + fields := issueTypes.Fields + for _, value := range fields { + for key, value := range value.(map[string]interface{}) { + if key == "required" && value == true { + requiredFields = requiredFields + 1 + } + } + + } + if requiredFields != 5 { + t.Errorf("Expected 5 required fields from Create Meta information, got %d", requiredFields) + } + } + } + +} + +func TestIssueService_GetEditMeta_Success(t *testing.T) { + setup() + defer teardown() + + testAPIEndpoint := "/rest/api/2/issue/PROJ-9001/editmeta" + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + + fmt.Fprint(w, `{ + "fields": { + "summary": { + "required": true, + "schema": { + "type": "string", + "system": "summary" + }, + "name": "Summary", + "hasDefaultValue": false, + "operations": [ + "set" + ] + }, + "attachment": { + "required": false, + "schema": { + "type": "array", + "items": "attachment", + "system": "attachment" + }, + "name": "Attachment", + "hasDefaultValue": false, + "operations": [ + + ] + } + } + }`) + }) + + editMeta, _, err := testClient.Issue.GetEditMeta(&Issue{Key: "PROJ-9001"}) + if err != nil { + t.Errorf("Expected nil error but got %s", err) + } + + requiredFields := 0 + fields := editMeta.Fields + for _, value := range fields { + for key, value := range value.(map[string]interface{}) { + if key == "required" && value == true { + requiredFields = requiredFields + 1 + } + } + + } + summary := fields["summary"].(map[string]interface{}) + attachment := fields["attachment"].(map[string]interface{}) + if summary["required"] != true { + t.Error("Expected summary to be required") + } + if attachment["required"] != false { + t.Error("Expected attachment to not be required") + } +} + +func TestIssueService_GetEditMeta_Fail(t *testing.T) { + _, _, err := testClient.Issue.GetEditMeta(&Issue{Key: "PROJ-9001"}) + if err == nil { + t.Error("Expected to receive an error, received nil instead") + } + + if _, ok := err.(*url.Error); !ok { + t.Errorf("Expected to receive an *url.Error, got %T instead", err) + } +} + +func TestMetaIssueType_GetCreateMetaWithOptions(t *testing.T) { + setup() + defer teardown() + + testAPIEndpoint := "/rest/api/2/issue/createmeta" + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + + fmt.Fprint(w, `{ + "expand": "projects", + "projects": [{ + "expand": "issuetypes", + "self": "https://my.jira.com/rest/api/2/project/11300", + "id": "11300", + "key": "SPN", + "name": "Super Project Name", + "avatarUrls": { + "48x48": "https://my.jira.com/secure/projectavatar?pid=11300&avatarId=14405", + "24x24": "https://my.jira.com/secure/projectavatar?size=small&pid=11300&avatarId=14405", + "16x16": "https://my.jira.com/secure/projectavatar?size=xsmall&pid=11300&avatarId=14405", + "32x32": "https://my.jira.com/secure/projectavatar?size=medium&pid=11300&avatarId=14405" + }, + "issuetypes": [{ + "self": "https://my.jira.com/rest/api/2/issuetype/6", + "id": "6", + "description": "An issue which ideally should be able to be completed in one step", + "iconUrl": "https://my.jira.com/secure/viewavatar?size=xsmall&avatarId=14006&avatarType=issuetype", + "name": "Request", + "subtask": false, + "expand": "fields", + "fields": { + "summary": { + "required": true, + "schema": { + "type": "string", + "system": "summary" + }, + "name": "Summary", + "hasDefaultValue": false, + "operations": [ + "set" + ] + }, + "issuetype": { + "required": true, + "schema": { + "type": "issuetype", + "system": "issuetype" + }, + "name": "Issue Type", + "hasDefaultValue": false, + "operations": [ + + ], + "allowedValues": [{ + "self": "https://my.jira.com/rest/api/2/issuetype/6", + "id": "6", + "description": "An issue which ideally should be able to be completed in one step", + "iconUrl": "https://my.jira.com/secure/viewavatar?size=xsmall&avatarId=14006&avatarType=issuetype", + "name": "Request", + "subtask": false, + "avatarId": 14006 + }] + }, + "components": { + "required": true, + "schema": { + "type": "array", + "items": "component", + "system": "components" + }, + "name": "Component/s", + "hasDefaultValue": false, + "operations": [ + "add", + "set", + "remove" + ], + "allowedValues": [{ + "self": "https://my.jira.com/rest/api/2/component/14144", + "id": "14144", + "name": "Build automation", + "description": "Jenkins, webhooks, etc." + }, { + "self": "https://my.jira.com/rest/api/2/component/14149", + "id": "14149", + "name": "Caches and noSQL", + "description": "Cassandra, Memcached, Redis, Twemproxy, Xcache" + }, { + "self": "https://my.jira.com/rest/api/2/component/14152", + "id": "14152", + "name": "Cloud services", + "description": "AWS and similar services" + }, { + "self": "https://my.jira.com/rest/api/2/component/14147", + "id": "14147", + "name": "Code quality tools", + "description": "Code sniffer, Sonar" + }, { + "self": "https://my.jira.com/rest/api/2/component/14156", + "id": "14156", + "name": "Configuration management and provisioning", + "description": "Apache/PHP modules, Consul, Salt" + }, { + "self": "https://my.jira.com/rest/api/2/component/13606", + "id": "13606", + "name": "Cronjobs", + "description": "Cronjobs in general" + }, { + "self": "https://my.jira.com/rest/api/2/component/14150", + "id": "14150", + "name": "Data pipelines and queues", + "description": "Kafka, RabbitMq" + }, { + "self": "https://my.jira.com/rest/api/2/component/14159", + "id": "14159", + "name": "Database", + "description": "MySQL related problems" + }, { + "self": "https://my.jira.com/rest/api/2/component/14314", + "id": "14314", + "name": "Documentation" + }, { + "self": "https://my.jira.com/rest/api/2/component/14151", + "id": "14151", + "name": "Git", + "description": "Bitbucket, GitHub, GitLab, Git in general" + }, { + "self": "https://my.jira.com/rest/api/2/component/14155", + "id": "14155", + "name": "HTTP services", + "description": "CDN, HaProxy, HTTP, Varnish" + }, { + "self": "https://my.jira.com/rest/api/2/component/14154", + "id": "14154", + "name": "Job and service scheduling", + "description": "Chronos, Docker, Marathon, Mesos" + }, { + "self": "https://my.jira.com/rest/api/2/component/14158", + "id": "14158", + "name": "Legacy", + "description": "Everything related to legacy" + }, { + "self": "https://my.jira.com/rest/api/2/component/14157", + "id": "14157", + "name": "Monitoring", + "description": "Collectd, Nagios, Monitoring in general" + }, { + "self": "https://my.jira.com/rest/api/2/component/14148", + "id": "14148", + "name": "Other services" + }, { + "self": "https://my.jira.com/rest/api/2/component/13602", + "id": "13602", + "name": "Package management", + "description": "Composer, Medusa, Satis" + }, { + "self": "https://my.jira.com/rest/api/2/component/14145", + "id": "14145", + "name": "Release", + "description": "Directory config, release queries, rewrite rules" + }, { + "self": "https://my.jira.com/rest/api/2/component/14146", + "id": "14146", + "name": "Staging systems and VMs", + "description": "Stage, QA machines, KVMs,Vagrant" + }, { + "self": "https://my.jira.com/rest/api/2/component/14153", + "id": "14153", + "name": "Blog" + }, { + "self": "https://my.jira.com/rest/api/2/component/14143", + "id": "14143", + "name": "Test automation", + "description": "Testing infrastructure in general" + }, { + "self": "https://my.jira.com/rest/api/2/component/14221", + "id": "14221", + "name": "Internal Infrastructure" + }] + }, + "attachment": { + "required": false, + "schema": { + "type": "array", + "items": "attachment", + "system": "attachment" + }, + "name": "Attachment", + "hasDefaultValue": false, + "operations": [ + + ] + }, + "duedate": { + "required": false, + "schema": { + "type": "date", + "system": "duedate" + }, + "name": "Due Date", + "hasDefaultValue": false, + "operations": [ + "set" + ] + }, + "description": { + "required": false, + "schema": { + "type": "string", + "system": "description" + }, + "name": "Description", + "hasDefaultValue": false, + "operations": [ + "set" + ] + }, + "customfield_10806": { + "required": false, + "schema": { + "type": "any", + "custom": "com.pyxis.greenhopper.jira:gh-epic-link", + "customId": 10806 + }, + "name": "Epic Link", + "hasDefaultValue": false, + "operations": [ + "set" + ] + }, + "project": { + "required": true, + "schema": { + "type": "project", + "system": "project" + }, + "name": "Project", + "hasDefaultValue": false, + "operations": [ + "set" + ], + "allowedValues": [{ + "self": "https://my.jira.com/rest/api/2/project/11300", + "id": "11300", + "key": "SPN", + "name": "Super Project Name", + "avatarUrls": { + "48x48": "https://my.jira.com/secure/projectavatar?pid=11300&avatarId=14405", + "24x24": "https://my.jira.com/secure/projectavatar?size=small&pid=11300&avatarId=14405", + "16x16": "https://my.jira.com/secure/projectavatar?size=xsmall&pid=11300&avatarId=14405", + "32x32": "https://my.jira.com/secure/projectavatar?size=medium&pid=11300&avatarId=14405" + }, + "projectCategory": { + "self": "https://my.jira.com/rest/api/2/projectCategory/10100", + "id": "10100", + "description": "", + "name": "Product & Development" + } + }] + }, + "assignee": { + "required": true, + "schema": { + "type": "user", + "system": "assignee" + }, + "name": "Assignee", + "autoCompleteUrl": "https://my.jira.com/rest/api/latest/user/assignable/search?issueKey=null&username=", + "hasDefaultValue": true, + "operations": [ + "set" + ] + }, + "priority": { + "required": false, + "schema": { + "type": "priority", + "system": "priority" + }, + "name": "Priority", + "hasDefaultValue": true, + "operations": [ + "set" + ], + "allowedValues": [{ + "self": "https://my.jira.com/rest/api/2/priority/1", + "iconUrl": "https://my.jira.com/images/icons/priorities/blocker.svg", + "name": "Immediate", + "id": "1" + }, { + "self": "https://my.jira.com/rest/api/2/priority/2", + "iconUrl": "https://my.jira.com/images/icons/priorities/critical.svg", + "name": "Urgent", + "id": "2" + }, { + "self": "https://my.jira.com/rest/api/2/priority/3", + "iconUrl": "https://my.jira.com/images/icons/priorities/major.svg", + "name": "High", + "id": "3" + }, { + "self": "https://my.jira.com/rest/api/2/priority/6", + "iconUrl": "https://my.jira.com/images/icons/priorities/moderate.svg", + "name": "Moderate", + "id": "6" + }, { + "self": "https://my.jira.com/rest/api/2/priority/4", + "iconUrl": "https://my.jira.com/images/icons/priorities/minor.svg", + "name": "Normal", + "id": "4" + }, { + "self": "https://my.jira.com/rest/api/2/priority/5", + "iconUrl": "https://my.jira.com/images/icons/priorities/trivial.svg", + "name": "Low", + "id": "5" + }] + }, + "labels": { + "required": false, + "schema": { + "type": "array", + "items": "string", + "system": "labels" + }, + "name": "Labels", + "autoCompleteUrl": "https://my.jira.com/rest/api/1.0/labels/suggest?query=", + "hasDefaultValue": false, + "operations": [ + "add", + "set", + "remove" + ] + } + } + }] + }] + }`) + }) + + issue, _, err := testClient.Issue.GetCreateMetaWithOptions(&GetQueryOptions{Expand: "projects.issuetypes.fields"}) + if err != nil { + t.Errorf("Expected nil error but got %s", err) + } + + if len(issue.Projects) != 1 { + t.Errorf("Expected 1 project, got %d", len(issue.Projects)) + } + for _, project := range issue.Projects { + if len(project.IssueTypes) != 1 { + t.Errorf("Expected 1 issueTypes, got %d", len(project.IssueTypes)) + } + for _, issueTypes := range project.IssueTypes { + requiredFields := 0 + fields := issueTypes.Fields + for _, value := range fields { + for key, value := range value.(map[string]interface{}) { + if key == "required" && value == true { + requiredFields = requiredFields + 1 + } + } + + } + if requiredFields != 5 { + t.Errorf("Expected 5 required fields from Create Meta information, got %d", requiredFields) + } + } + } +} + +func TestMetaIssueType_GetMandatoryFields(t *testing.T) { + data := make(map[string]interface{}) + + data["summary"] = map[string]interface{}{ + "required": true, + "name": "Summary", + } + + data["components"] = map[string]interface{}{ + "required": true, + "name": "Components", + } + + data["epicLink"] = map[string]interface{}{ + "required": false, + "name": "Epic Link", + } + + m := new(MetaIssueType) + m.Fields = data + + mandatory, err := m.GetMandatoryFields() + if err != nil { + t.Errorf("Expected nil error, received %s", err) + } + + if len(mandatory) != 2 { + t.Errorf("Expected 2 received %+v", mandatory) + } +} + +func TestMetaIssueType_GetMandatoryFields_NonExistentRequiredKey_Fail(t *testing.T) { + data := make(map[string]interface{}) + + data["summary"] = map[string]interface{}{ + "name": "Summary", + } + + m := new(MetaIssueType) + m.Fields = data + + _, err := m.GetMandatoryFields() + if err == nil { + t.Error("Expected non nil errpr, received nil") + } +} + +func TestMetaIssueType_GetMandatoryFields_NonExistentNameKey_Fail(t *testing.T) { + data := make(map[string]interface{}) + + data["summary"] = map[string]interface{}{ + "required": true, + } + + m := new(MetaIssueType) + m.Fields = data + + _, err := m.GetMandatoryFields() + if err == nil { + t.Error("Expected non nil errpr, received nil") + } +} + +func TestMetaIssueType_GetAllFields(t *testing.T) { + data := make(map[string]interface{}) + + data["summary"] = map[string]interface{}{ + "required": true, + "name": "Summary", + } + + data["components"] = map[string]interface{}{ + "required": true, + "name": "Components", + } + + data["epicLink"] = map[string]interface{}{ + "required": false, + "name": "Epic Link", + } + + m := new(MetaIssueType) + m.Fields = data + + mandatory, err := m.GetAllFields() + + if err != nil { + t.Errorf("Expected nil err, received %s", err) + } + + if len(mandatory) != 3 { + t.Errorf("Expected 3 received %+v", mandatory) + } +} + +func TestMetaIssueType_GetAllFields_NonExistingNameKey_Fail(t *testing.T) { + data := make(map[string]interface{}) + + data["summary"] = map[string]interface{}{ + "required": true, + } + + m := new(MetaIssueType) + m.Fields = data + + _, err := m.GetAllFields() + if err == nil { + t.Error("Expected non nil error, received nil") + } +} + +func TestMetaIssueType_CheckCompleteAndAvailable_MandatoryMissing(t *testing.T) { + data := make(map[string]interface{}) + + data["summary"] = map[string]interface{}{ + "required": true, + "name": "Summary", + } + + data["someKey"] = map[string]interface{}{ + "required": false, + "name": "SomeKey", + } + + config := map[string]string{ + "SomeKey": "somevalue", + } + + m := new(MetaIssueType) + m.Fields = data + + ok, err := m.CheckCompleteAndAvailable(config) + if err == nil { + t.Error("Expected non nil error. Received nil") + } + + if ok != false { + t.Error("Expected false, got true") + } + +} + +func TestMetaIssueType_CheckCompleteAndAvailable_NotAvailable(t *testing.T) { + data := make(map[string]interface{}) + + data["summary"] = map[string]interface{}{ + "required": true, + "name": "Summary", + } + + config := map[string]string{ + "Summary": "Issue Summary", + "SomeKey": "somevalue", + } + + m := new(MetaIssueType) + m.Fields = data + + ok, err := m.CheckCompleteAndAvailable(config) + if err == nil { + t.Error("Expected non nil error. Received nil") + } + + if ok != false { + t.Error("Expected false, got true") + } + +} + +func TestMetaIssueType_CheckCompleteAndAvailable_Success(t *testing.T) { + data := make(map[string]interface{}) + + data["summary"] = map[string]interface{}{ + "required": true, + "name": "Summary", + } + + data["someKey"] = map[string]interface{}{ + "required": false, + "name": "SomeKey", + } + + config := map[string]string{ + "SomeKey": "somevalue", + "Summary": "Issue summary", + } + + m := new(MetaIssueType) + m.Fields = data + + ok, err := m.CheckCompleteAndAvailable(config) + if err != nil { + t.Errorf("Expected nil error. Received %s", err) + } + + if ok != true { + t.Error("Expected true, got false") + } + +} + +func TestCreateMetaInfo_GetProjectWithName_Success(t *testing.T) { + metainfo := new(CreateMetaInfo) + metainfo.Projects = append(metainfo.Projects, &MetaProject{ + Name: "SPN", + }) + + project := metainfo.GetProjectWithName("SPN") + if project == nil { + t.Errorf("Expected non nil value, received nil") + } +} + +func TestMetaProject_GetIssueTypeWithName_CaseMismatch_Success(t *testing.T) { + m := new(MetaProject) + m.IssueTypes = append(m.IssueTypes, &MetaIssueType{ + Name: "Bug", + }) + + issuetype := m.GetIssueTypeWithName("BUG") + + if issuetype == nil { + t.Errorf("Expected non nil value, received nil") + } +} + +func TestCreateMetaInfo_GetProjectWithKey_Success(t *testing.T) { + metainfo := new(CreateMetaInfo) + metainfo.Projects = append(metainfo.Projects, &MetaProject{ + Key: "SPNKEY", + }) + + project := metainfo.GetProjectWithKey("SPNKEY") + if project == nil { + t.Errorf("Expected non nil value, received nil") + } +} + +func TestCreateMetaInfo_GetProjectWithKey_NilForNonExistent(t *testing.T) { + metainfo := new(CreateMetaInfo) + metainfo.Projects = append(metainfo.Projects, &MetaProject{ + Key: "SPNKEY", + }) + + project := metainfo.GetProjectWithKey("SPN") + if project != nil { + t.Errorf("Expected nil, received value") + } +} diff --git a/onpremise/organization.go b/onpremise/organization.go new file mode 100644 index 0000000..373479b --- /dev/null +++ b/onpremise/organization.go @@ -0,0 +1,397 @@ +package onpremise + +import ( + "context" + "fmt" +) + +// OrganizationService handles Organizations for the Jira instance / API. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/ +type OrganizationService struct { + client *Client +} + +// OrganizationCreationDTO is DTO for creat organization API +type OrganizationCreationDTO struct { + Name string `json:"name,omitempty" structs:"name,omitempty"` +} + +// SelfLink Stores REST API URL to the organization. +type SelfLink struct { + Self string `json:"self,omitempty" structs:"self,omitempty"` +} + +// Organization contains Organization data +type Organization struct { + ID string `json:"id,omitempty" structs:"id,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + Links *SelfLink `json:"_links,omitempty" structs:"_links,omitempty"` +} + +// OrganizationUsersDTO contains organization user ids +type OrganizationUsersDTO struct { + AccountIds []string `json:"accountIds,omitempty" structs:"accountIds,omitempty"` +} + +// PagedDTO is response of a paged list +type PagedDTO struct { + Size int `json:"size,omitempty" structs:"size,omitempty"` + Start int `json:"start,omitempty" structs:"start,omitempty"` + Limit int `limit:"size,omitempty" structs:"limit,omitempty"` + IsLastPage bool `json:"isLastPage,omitempty" structs:"isLastPage,omitempty"` + Values []interface{} `values:"isLastPage,omitempty" structs:"values,omitempty"` + Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"` +} + +// PropertyKey contains Property key details. +type PropertyKey struct { + Self string `json:"self,omitempty" structs:"self,omitempty"` + Key string `json:"key,omitempty" structs:"key,omitempty"` +} + +// PropertyKeys contains an array of PropertyKey +type PropertyKeys struct { + Keys []PropertyKey `json:"keys,omitempty" structs:"keys,omitempty"` +} + +// GetAllOrganizationsWithContext returns a list of organizations in +// the Jira Service Management instance. +// Use this method when you want to present a list +// of organizations or want to locate an organization +// by name. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-group-organization +func (s *OrganizationService) GetAllOrganizationsWithContext(ctx context.Context, start int, limit int, accountID string) (*PagedDTO, *Response, error) { + apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization?start=%d&limit=%d", start, limit) + if accountID != "" { + apiEndPoint += fmt.Sprintf("&accountId=%s", accountID) + } + + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req.Header.Set("Accept", "application/json") + + if err != nil { + return nil, nil, err + } + + v := new(PagedDTO) + resp, err := s.client.Do(req, v) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return v, resp, nil +} + +// GetAllOrganizations wraps GetAllOrganizationsWithContext using the background context. +func (s *OrganizationService) GetAllOrganizations(start int, limit int, accountID string) (*PagedDTO, *Response, error) { + return s.GetAllOrganizationsWithContext(context.Background(), start, limit, accountID) +} + +// CreateOrganizationWithContext creates an organization by +// passing the name of the organization. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-post +func (s *OrganizationService) CreateOrganizationWithContext(ctx context.Context, name string) (*Organization, *Response, error) { + apiEndPoint := "rest/servicedeskapi/organization" + + organization := OrganizationCreationDTO{ + Name: name, + } + + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, organization) + req.Header.Set("Accept", "application/json") + + if err != nil { + return nil, nil, err + } + + o := new(Organization) + resp, err := s.client.Do(req, &o) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return o, resp, nil +} + +// CreateOrganization wraps CreateOrganizationWithContext using the background context. +func (s *OrganizationService) CreateOrganization(name string) (*Organization, *Response, error) { + return s.CreateOrganizationWithContext(context.Background(), name) +} + +// GetOrganizationWithContext returns details of an +// organization. Use this method to get organization +// details whenever your application component is +// passed an organization ID but needs to display +// other organization details. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-get +func (s *OrganizationService) GetOrganizationWithContext(ctx context.Context, organizationID int) (*Organization, *Response, error) { + apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) + + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req.Header.Set("Accept", "application/json") + + if err != nil { + return nil, nil, err + } + + o := new(Organization) + resp, err := s.client.Do(req, &o) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return o, resp, nil +} + +// GetOrganization wraps GetOrganizationWithContext using the background context. +func (s *OrganizationService) GetOrganization(organizationID int) (*Organization, *Response, error) { + return s.GetOrganizationWithContext(context.Background(), organizationID) +} + +// DeleteOrganizationWithContext deletes an organization. Note that +// the organization is deleted regardless +// of other associations it may have. +// For example, associations with service desks. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-delete +// Caller must close resp.Body +func (s *OrganizationService) DeleteOrganizationWithContext(ctx context.Context, organizationID int) (*Response, error) { + apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) + + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil) + + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return resp, jerr + } + + return resp, nil +} + +// DeleteOrganization wraps DeleteOrganizationWithContext using the background context. +// Caller must close resp.Body +func (s *OrganizationService) DeleteOrganization(organizationID int) (*Response, error) { + return s.DeleteOrganizationWithContext(context.Background(), organizationID) +} + +// GetPropertiesKeysWithContext returns the keys of +// all properties for an organization. Use this resource +// when you need to find out what additional properties +// items have been added to an organization. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-get +func (s *OrganizationService) GetPropertiesKeysWithContext(ctx context.Context, organizationID int) (*PropertyKeys, *Response, error) { + apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property", organizationID) + + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req.Header.Set("Accept", "application/json") + + if err != nil { + return nil, nil, err + } + + pk := new(PropertyKeys) + resp, err := s.client.Do(req, &pk) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return pk, resp, nil +} + +// GetPropertiesKeys wraps GetPropertiesKeysWithContext using the background context. +func (s *OrganizationService) GetPropertiesKeys(organizationID int) (*PropertyKeys, *Response, error) { + return s.GetPropertiesKeysWithContext(context.Background(), organizationID) +} + +// GetPropertyWithContext returns the value of a property +// from an organization. Use this method to obtain the JSON +// content for an organization's property. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-get +func (s *OrganizationService) GetPropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*EntityProperty, *Response, error) { + apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) + + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req.Header.Set("Accept", "application/json") + + if err != nil { + return nil, nil, err + } + + ep := new(EntityProperty) + resp, err := s.client.Do(req, &ep) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return ep, resp, nil +} + +// GetProperty wraps GetPropertyWithContext using the background context. +func (s *OrganizationService) GetProperty(organizationID int, propertyKey string) (*EntityProperty, *Response, error) { + return s.GetPropertyWithContext(context.Background(), organizationID, propertyKey) +} + +// SetPropertyWithContext sets the value of a +// property for an organization. Use this +// resource to store custom data against an organization. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-put +// Caller must close resp.Body +func (s *OrganizationService) SetPropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { + apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) + + req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndPoint, nil) + req.Header.Set("Accept", "application/json") + + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return resp, jerr + } + + return resp, nil +} + +// SetProperty wraps SetPropertyWithContext using the background context. +// Caller must close resp.Body +func (s *OrganizationService) SetProperty(organizationID int, propertyKey string) (*Response, error) { + return s.SetPropertyWithContext(context.Background(), organizationID, propertyKey) +} + +// DeletePropertyWithContext removes a property from an organization. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-delete +// Caller must close resp.Body +func (s *OrganizationService) DeletePropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { + apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) + + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil) + req.Header.Set("Accept", "application/json") + + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return resp, jerr + } + + return resp, nil +} + +// DeleteProperty wraps DeletePropertyWithContext using the background context. +// Caller must close resp.Body +func (s *OrganizationService) DeleteProperty(organizationID int, propertyKey string) (*Response, error) { + return s.DeletePropertyWithContext(context.Background(), organizationID, propertyKey) +} + +// GetUsersWithContext returns all the users +// associated with an organization. Use this +// method where you want to provide a list of +// users for an organization or determine if +// a user is associated with an organization. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-get +func (s *OrganizationService) GetUsersWithContext(ctx context.Context, organizationID int, start int, limit int) (*PagedDTO, *Response, error) { + apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user?start=%d&limit=%d", organizationID, start, limit) + + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req.Header.Set("Accept", "application/json") + + if err != nil { + return nil, nil, err + } + + users := new(PagedDTO) + resp, err := s.client.Do(req, &users) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return users, resp, nil +} + +// GetUsers wraps GetUsersWithContext using the background context. +func (s *OrganizationService) GetUsers(organizationID int, start int, limit int) (*PagedDTO, *Response, error) { + return s.GetUsersWithContext(context.Background(), organizationID, start, limit) +} + +// AddUsersWithContext adds users to an organization. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-post +// Caller must close resp.Body +func (s *OrganizationService) AddUsersWithContext(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { + apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) + + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, users) + + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return resp, jerr + } + + return resp, nil +} + +// AddUsers wraps AddUsersWithContext using the background context. +// Caller must close resp.Body +func (s *OrganizationService) AddUsers(organizationID int, users OrganizationUsersDTO) (*Response, error) { + return s.AddUsersWithContext(context.Background(), organizationID, users) +} + +// RemoveUsersWithContext removes users from an organization. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-delete +// Caller must close resp.Body +func (s *OrganizationService) RemoveUsersWithContext(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { + apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) + + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil) + req.Header.Set("Accept", "application/json") + + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return resp, jerr + } + + return resp, nil +} + +// RemoveUsers wraps RemoveUsersWithContext using the background context. +// Caller must close resp.Body +func (s *OrganizationService) RemoveUsers(organizationID int, users OrganizationUsersDTO) (*Response, error) { + return s.RemoveUsersWithContext(context.Background(), organizationID, users) +} diff --git a/onpremise/organization_test.go b/onpremise/organization_test.go new file mode 100644 index 0000000..809d9ee --- /dev/null +++ b/onpremise/organization_test.go @@ -0,0 +1,325 @@ +package onpremise + +import ( + "encoding/json" + "fmt" + "net/http" + "testing" +) + +func TestOrganizationService_GetAllOrganizationsWithContext(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/organization", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/servicedeskapi/organization") + + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{ "_expands": [], "size": 1, "start": 1, "limit": 1, "isLastPage": false, "_links": { "base": "https://your-domain.atlassian.net/rest/servicedeskapi", "context": "context", "next": "https://your-domain.atlassian.net/rest/servicedeskapi/organization?start=2&limit=1", "prev": "https://your-domain.atlassian.net/rest/servicedeskapi/organization?start=0&limit=1" }, "values": [ { "id": "1", "name": "Charlie Cakes Franchises", "_links": { "self": "https://your-domain.atlassian.net/rest/servicedeskapi/organization/1" } } ] }`) + }) + + result, _, err := testClient.Organization.GetAllOrganizations(0, 50, "") + + if result == nil { + t.Error("Expected Organizations. Result is nil") + } else if result.Size != 1 { + t.Errorf("Expected size to be 1, but got %d", result.Size) + } + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestOrganizationService_CreateOrganization(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/organization", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/servicedeskapi/organization") + + o := new(OrganizationCreationDTO) + json.NewDecoder(r.Body).Decode(&o) + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, `{ "id": "1", "name": "%s", "_links": { "self": "https://your-domain.atlassian.net/rest/servicedeskapi/organization/1" } }`, o.Name) + }) + + name := "MyOrg" + o, _, err := testClient.Organization.CreateOrganization(name) + + if o == nil { + t.Error("Expected Organization. Result is nil") + } else if o.Name != name { + t.Errorf("Expected name to be %s, but got %s", name, o.Name) + } + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestOrganizationService_GetOrganization(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/organization/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/servicedeskapi/organization/1") + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, `{ "id": "1", "name": "name", "_links": { "self": "https://your-domain.atlassian.net/rest/servicedeskapi/organization/1" } }`) + }) + + id := 1 + o, _, err := testClient.Organization.GetOrganization(id) + + if err != nil { + t.Errorf("Error given: %s", err) + } + + if o == nil { + t.Error("Expected Organization. Result is nil") + } else if o.Name != "name" { + t.Errorf("Expected name to be name, but got %s", o.Name) + } +} + +func TestOrganizationService_DeleteOrganization(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/organization/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, "/rest/servicedeskapi/organization/1") + + w.WriteHeader(http.StatusNoContent) + }) + + _, err := testClient.Organization.DeleteOrganization(1) + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestOrganizationService_GetPropertiesKeys(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/organization/1/property", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/servicedeskapi/organization/1/property") + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, `{ + "keys": [ + { + "self": "/rest/servicedeskapi/organization/1/property/propertyKey", + "key": "organization.attributes" + } + ] + }`) + }) + + pk, _, err := testClient.Organization.GetPropertiesKeys(1) + + if err != nil { + t.Errorf("Error given: %s", err) + } + + if pk == nil { + t.Error("Expected Keys. Result is nil") + } else if pk.Keys[0].Key != "organization.attributes" { + t.Errorf("Expected name to be organization.attributes, but got %s", pk.Keys[0].Key) + } +} + +func TestOrganizationService_GetProperty(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/organization/1/property/organization.attributes", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/servicedeskapi/organization/1/property/organization.attributes") + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, `{ + "key": "organization.attributes", + "value": { + "phone": "0800-1233456789", + "mail": "charlie@example.com" + } + }`) + }) + + key := "organization.attributes" + ep, _, err := testClient.Organization.GetProperty(1, key) + + if err != nil { + t.Errorf("Error given: %s", err) + } + + if ep == nil { + t.Error("Expected Entity. Result is nil") + } else if ep.Key != key { + t.Errorf("Expected name to be %s, but got %s", key, ep.Key) + } +} + +func TestOrganizationService_SetProperty(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/organization/1/property/organization.attributes", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testRequestURL(t, r, "/rest/servicedeskapi/organization/1/property/organization.attributes") + + w.WriteHeader(http.StatusOK) + }) + + key := "organization.attributes" + _, err := testClient.Organization.SetProperty(1, key) + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestOrganizationService_DeleteProperty(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/organization/1/property/organization.attributes", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, "/rest/servicedeskapi/organization/1/property/organization.attributes") + + w.WriteHeader(http.StatusOK) + }) + + key := "organization.attributes" + _, err := testClient.Organization.DeleteProperty(1, key) + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestOrganizationService_GetUsers(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/organization/1/user", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/servicedeskapi/organization/1/user") + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, `{ + "_expands": [], + "size": 1, + "start": 1, + "limit": 1, + "isLastPage": false, + "_links": { + "base": "https://your-domain.atlassian.net/rest/servicedeskapi", + "context": "context", + "next": "https://your-domain.atlassian.net/rest/servicedeskapi/organization/1/user?start=2&limit=1", + "prev": "https://your-domain.atlassian.net/rest/servicedeskapi/organization/1/user?start=0&limit=1" + }, + "values": [ + { + "accountId": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "name": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "key": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "emailAddress": "fred@example.com", + "displayName": "Fred F. User", + "active": true, + "timeZone": "Australia/Sydney", + "_links": { + "jiraRest": "https://your-domain.atlassian.net/rest/api/2/user?username=qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "avatarUrls": { + "48x48": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=48", + "24x24": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=24", + "16x16": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=16", + "32x32": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=32" + }, + "self": "https://your-domain.atlassian.net/rest/api/2/user?username=qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b" + } + }, + { + "accountId": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3a01db05e2a66fa80bd", + "name": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3a01db05e2a66fa80bd", + "key": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3a01db05e2a66fa80bd", + "emailAddress": "bob@example.com", + "displayName": "Bob D. Builder", + "active": true, + "timeZone": "Australia/Sydney", + "_links": { + "jiraRest": "https://your-domain.atlassian.net/rest/api/2/user?username=qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3a01db05e2a66fa80bd", + "avatarUrls": { + "48x48": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=48", + "24x24": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=24", + "16x16": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=16", + "32x32": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=32" + }, + "self": "https://your-domain.atlassian.net/rest/api/2/user?username=qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3a01db05e2a66fa80bd" + } + } + ] + }`) + }) + + users, _, err := testClient.Organization.GetUsers(1, 0, 50) + + if err != nil { + t.Errorf("Error given: %s", err) + } + + if users == nil { + t.Error("Expected Organizations. Result is nil") + } else if users.Size != 1 { + t.Errorf("Expected size to be 1, but got %d", users.Size) + } + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestOrganizationService_AddUsers(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/organization/1/user", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/servicedeskapi/organization/1/user") + + w.WriteHeader(http.StatusNoContent) + }) + + users := OrganizationUsersDTO{ + AccountIds: []string{ + "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3a01db05e2a66fa80bd", + }, + } + _, err := testClient.Organization.AddUsers(1, users) + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestOrganizationService_RemoveUsers(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/organization/1/user", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, "/rest/servicedeskapi/organization/1/user") + + w.WriteHeader(http.StatusNoContent) + }) + + users := OrganizationUsersDTO{ + AccountIds: []string{ + "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3a01db05e2a66fa80bd", + }, + } + _, err := testClient.Organization.RemoveUsers(1, users) + + if err != nil { + t.Errorf("Error given: %s", err) + } +} diff --git a/onpremise/permissionscheme.go b/onpremise/permissionscheme.go new file mode 100644 index 0000000..f81794e --- /dev/null +++ b/onpremise/permissionscheme.go @@ -0,0 +1,82 @@ +package onpremise + +import ( + "context" + "fmt" +) + +// PermissionSchemeService handles permissionschemes for the Jira instance / API. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Permissionscheme +type PermissionSchemeService struct { + client *Client +} +type PermissionSchemes struct { + PermissionSchemes []PermissionScheme `json:"permissionSchemes" structs:"permissionSchemes"` +} + +type Permission struct { + ID int `json:"id" structs:"id"` + Self string `json:"expand" structs:"expand"` + Holder Holder `json:"holder" structs:"holder"` + Name string `json:"permission" structs:"permission"` +} + +type Holder struct { + Type string `json:"type" structs:"type"` + Parameter string `json:"parameter" structs:"parameter"` + Expand string `json:"expand" structs:"expand"` +} + +// GetListWithContext returns a list of all permission schemes +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-get +func (s *PermissionSchemeService) GetListWithContext(ctx context.Context) (*PermissionSchemes, *Response, error) { + apiEndpoint := "/rest/api/3/permissionscheme" + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + pss := new(PermissionSchemes) + resp, err := s.client.Do(req, &pss) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return pss, resp, nil +} + +// GetList wraps GetListWithContext using the background context. +func (s *PermissionSchemeService) GetList() (*PermissionSchemes, *Response, error) { + return s.GetListWithContext(context.Background()) +} + +// GetWithContext returns a full representation of the permission scheme for the schemeID +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-schemeId-get +func (s *PermissionSchemeService) GetWithContext(ctx context.Context, schemeID int) (*PermissionScheme, *Response, error) { + apiEndpoint := fmt.Sprintf("/rest/api/3/permissionscheme/%d", schemeID) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + ps := new(PermissionScheme) + resp, err := s.client.Do(req, ps) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + if ps.Self == "" { + return nil, resp, fmt.Errorf("no permissionscheme with ID %d found", schemeID) + } + + return ps, resp, nil +} + +// Get wraps GetWithContext using the background context. +func (s *PermissionSchemeService) Get(schemeID int) (*PermissionScheme, *Response, error) { + return s.GetWithContext(context.Background(), schemeID) +} diff --git a/onpremise/permissionscheme_test.go b/onpremise/permissionscheme_test.go new file mode 100644 index 0000000..206efdf --- /dev/null +++ b/onpremise/permissionscheme_test.go @@ -0,0 +1,106 @@ +package onpremise + +import ( + "fmt" + "net/http" + "os" + "testing" +) + +func TestPermissionSchemeService_GetList(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/3/permissionscheme" + + raw, err := os.ReadFile("../testing/mock-data/all_permissionschemes.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + permissionScheme, _, err := testClient.PermissionScheme.GetList() + if err != nil { + t.Errorf("Error given: %v", err) + } + if permissionScheme == nil { + t.Error("Expected permissionScheme list. PermissionScheme list is nil") + return + } + if len(permissionScheme.PermissionSchemes) != 2 { + t.Errorf("Expected %d permissionSchemes but got %d", 2, len(permissionScheme.PermissionSchemes)) + } +} + +func TestPermissionSchemeService_GetList_NoList(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/3/permissionscheme" + + raw, err := os.ReadFile("../testing/mock-data/no_permissionschemes.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + permissionScheme, _, err := testClient.PermissionScheme.GetList() + if permissionScheme != nil { + t.Errorf("Expected permissionScheme list has %d entries but should be nil", len(permissionScheme.PermissionSchemes)) + } + if err == nil { + t.Errorf("No error given") + } +} + +func TestPermissionSchemeService_Get(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/3/permissionscheme/10100" + raw, err := os.ReadFile("../testing/mock-data/permissionscheme.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { + testMethod(t, request, "GET") + testRequestURL(t, request, testAPIEdpoint) + fmt.Fprint(writer, string(raw)) + }) + + permissionScheme, _, err := testClient.PermissionScheme.Get(10100) + if permissionScheme == nil { + t.Errorf("Expected permissionscheme, got nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestPermissionSchemeService_Get_NoScheme(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/3/permissionscheme/99999" + raw, err := os.ReadFile("../testing/mock-data/no_permissionscheme.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { + testMethod(t, request, "GET") + testRequestURL(t, request, testAPIEdpoint) + fmt.Fprint(writer, string(raw)) + }) + + permissionScheme, _, err := testClient.PermissionScheme.Get(99999) + if permissionScheme != nil { + t.Errorf("Expected nil, got permissionschme %v", permissionScheme) + } + if err == nil { + t.Errorf("No error given") + } +} diff --git a/onpremise/priority.go b/onpremise/priority.go new file mode 100644 index 0000000..1d3e46b --- /dev/null +++ b/onpremise/priority.go @@ -0,0 +1,44 @@ +package onpremise + +import "context" + +// PriorityService handles priorities for the Jira instance / API. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Priority +type PriorityService struct { + client *Client +} + +// Priority represents a priority of a Jira issue. +// Typical types are "Normal", "Moderate", "Urgent", ... +type Priority struct { + Self string `json:"self,omitempty" structs:"self,omitempty"` + IconURL string `json:"iconUrl,omitempty" structs:"iconUrl,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + ID string `json:"id,omitempty" structs:"id,omitempty"` + StatusColor string `json:"statusColor,omitempty" structs:"statusColor,omitempty"` + Description string `json:"description,omitempty" structs:"description,omitempty"` +} + +// GetListWithContext gets all priorities from Jira +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-priority-get +func (s *PriorityService) GetListWithContext(ctx context.Context) ([]Priority, *Response, error) { + apiEndpoint := "rest/api/2/priority" + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + priorityList := []Priority{} + resp, err := s.client.Do(req, &priorityList) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return priorityList, resp, nil +} + +// GetList wraps GetListWithContext using the background context. +func (s *PriorityService) GetList() ([]Priority, *Response, error) { + return s.GetListWithContext(context.Background()) +} diff --git a/onpremise/priority_test.go b/onpremise/priority_test.go new file mode 100644 index 0000000..e2ca273 --- /dev/null +++ b/onpremise/priority_test.go @@ -0,0 +1,32 @@ +package onpremise + +import ( + "fmt" + "net/http" + "os" + "testing" +) + +func TestPriorityService_GetList(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/2/priority" + + raw, err := os.ReadFile("../testing/mock-data/all_priorities.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, string(raw)) + }) + + priorities, _, err := testClient.Priority.GetList() + if priorities == nil { + t.Error("Expected priority list. Priority list is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} diff --git a/onpremise/project.go b/onpremise/project.go new file mode 100644 index 0000000..47782f4 --- /dev/null +++ b/onpremise/project.go @@ -0,0 +1,182 @@ +package onpremise + +import ( + "context" + "fmt" + + "github.com/google/go-querystring/query" +) + +// ProjectService handles projects for the Jira instance / API. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project +type ProjectService struct { + client *Client +} + +// ProjectList represent a list of Projects +type ProjectList []struct { + Expand string `json:"expand" structs:"expand"` + Self string `json:"self" structs:"self"` + ID string `json:"id" structs:"id"` + Key string `json:"key" structs:"key"` + Name string `json:"name" structs:"name"` + AvatarUrls AvatarUrls `json:"avatarUrls" structs:"avatarUrls"` + ProjectTypeKey string `json:"projectTypeKey" structs:"projectTypeKey"` + ProjectCategory ProjectCategory `json:"projectCategory,omitempty" structs:"projectsCategory,omitempty"` + IssueTypes []IssueType `json:"issueTypes,omitempty" structs:"issueTypes,omitempty"` +} + +// ProjectCategory represents a single project category +type ProjectCategory struct { + Self string `json:"self" structs:"self,omitempty"` + ID string `json:"id" structs:"id,omitempty"` + Name string `json:"name" structs:"name,omitempty"` + Description string `json:"description" structs:"description,omitempty"` +} + +// Project represents a Jira Project. +type Project struct { + Expand string `json:"expand,omitempty" structs:"expand,omitempty"` + Self string `json:"self,omitempty" structs:"self,omitempty"` + ID string `json:"id,omitempty" structs:"id,omitempty"` + Key string `json:"key,omitempty" structs:"key,omitempty"` + Description string `json:"description,omitempty" structs:"description,omitempty"` + Lead User `json:"lead,omitempty" structs:"lead,omitempty"` + Components []ProjectComponent `json:"components,omitempty" structs:"components,omitempty"` + IssueTypes []IssueType `json:"issueTypes,omitempty" structs:"issueTypes,omitempty"` + URL string `json:"url,omitempty" structs:"url,omitempty"` + Email string `json:"email,omitempty" structs:"email,omitempty"` + AssigneeType string `json:"assigneeType,omitempty" structs:"assigneeType,omitempty"` + Versions []Version `json:"versions,omitempty" structs:"versions,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + Roles map[string]string `json:"roles,omitempty" structs:"roles,omitempty"` + AvatarUrls AvatarUrls `json:"avatarUrls,omitempty" structs:"avatarUrls,omitempty"` + ProjectCategory ProjectCategory `json:"projectCategory,omitempty" structs:"projectCategory,omitempty"` +} + +// ProjectComponent represents a single component of a project +type ProjectComponent struct { + Self string `json:"self" structs:"self,omitempty"` + ID string `json:"id" structs:"id,omitempty"` + Name string `json:"name" structs:"name,omitempty"` + Description string `json:"description" structs:"description,omitempty"` + Lead User `json:"lead,omitempty" structs:"lead,omitempty"` + AssigneeType string `json:"assigneeType" structs:"assigneeType,omitempty"` + Assignee User `json:"assignee" structs:"assignee,omitempty"` + RealAssigneeType string `json:"realAssigneeType" structs:"realAssigneeType,omitempty"` + RealAssignee User `json:"realAssignee" structs:"realAssignee,omitempty"` + IsAssigneeTypeValid bool `json:"isAssigneeTypeValid" structs:"isAssigneeTypeValid,omitempty"` + Project string `json:"project" structs:"project,omitempty"` + ProjectID int `json:"projectId" structs:"projectId,omitempty"` +} + +// PermissionScheme represents the permission scheme for the project +type PermissionScheme struct { + Expand string `json:"expand" structs:"expand,omitempty"` + Self string `json:"self" structs:"self,omitempty"` + ID int `json:"id" structs:"id,omitempty"` + Name string `json:"name" structs:"name,omitempty"` + Description string `json:"description" structs:"description,omitempty"` + Permissions []Permission `json:"permissions" structs:"permissions,omitempty"` +} + +// GetListWithContext gets all projects form Jira +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects +func (s *ProjectService) GetListWithContext(ctx context.Context) (*ProjectList, *Response, error) { + return s.ListWithOptionsWithContext(ctx, &GetQueryOptions{}) +} + +// GetList wraps GetListWithContext using the background context. +func (s *ProjectService) GetList() (*ProjectList, *Response, error) { + return s.GetListWithContext(context.Background()) +} + +// ListWithOptionsWithContext gets all projects form Jira with optional query params, like &GetQueryOptions{Expand: "issueTypes"} to get +// a list of all projects and their supported issuetypes +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects +func (s *ProjectService) ListWithOptionsWithContext(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { + apiEndpoint := "rest/api/2/project" + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + if options != nil { + q, err := query.Values(options) + if err != nil { + return nil, nil, err + } + req.URL.RawQuery = q.Encode() + } + + projectList := new(ProjectList) + resp, err := s.client.Do(req, projectList) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return projectList, resp, nil +} + +// ListWithOptions wraps ListWithOptionsWithContext using the background context. +func (s *ProjectService) ListWithOptions(options *GetQueryOptions) (*ProjectList, *Response, error) { + return s.ListWithOptionsWithContext(context.Background(), options) +} + +// GetWithContext returns a full representation of the project for the given issue key. +// Jira will attempt to identify the project by the projectIdOrKey path parameter. +// This can be an project id, or an project key. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject +func (s *ProjectService) GetWithContext(ctx context.Context, projectID string) (*Project, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/project/%s", projectID) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + project := new(Project) + resp, err := s.client.Do(req, project) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return project, resp, nil +} + +// Get wraps GetWithContext using the background context. +func (s *ProjectService) Get(projectID string) (*Project, *Response, error) { + return s.GetWithContext(context.Background(), projectID) +} + +// GetPermissionSchemeWithContext returns a full representation of the permission scheme for the project +// Jira will attempt to identify the project by the projectIdOrKey path parameter. +// This can be an project id, or an project key. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject +func (s *ProjectService) GetPermissionSchemeWithContext(ctx context.Context, projectID string) (*PermissionScheme, *Response, error) { + apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s/permissionscheme", projectID) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + ps := new(PermissionScheme) + resp, err := s.client.Do(req, ps) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return ps, resp, nil +} + +// GetPermissionScheme wraps GetPermissionSchemeWithContext using the background context. +func (s *ProjectService) GetPermissionScheme(projectID string) (*PermissionScheme, *Response, error) { + return s.GetPermissionSchemeWithContext(context.Background(), projectID) +} diff --git a/onpremise/project_test.go b/onpremise/project_test.go new file mode 100644 index 0000000..21e583a --- /dev/null +++ b/onpremise/project_test.go @@ -0,0 +1,162 @@ +package onpremise + +import ( + "fmt" + "net/http" + "os" + "testing" +) + +func TestProjectService_GetList(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/2/project" + + raw, err := os.ReadFile("../testing/mock-data/all_projects.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, string(raw)) + }) + + projects, _, err := testClient.Project.GetList() + if projects == nil { + t.Error("Expected project list. Project list is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestProjectService_ListWithOptions(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/2/project" + + raw, err := os.ReadFile("../testing/mock-data/all_projects.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/project?expand=issueTypes") + fmt.Fprint(w, string(raw)) + }) + + projects, _, err := testClient.Project.ListWithOptions(&GetQueryOptions{Expand: "issueTypes"}) + if projects == nil { + t.Error("Expected project list. Project list is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestProjectService_Get(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/2/project/12310505" + + raw, err := os.ReadFile("../testing/mock-data/project.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, string(raw)) + }) + + projects, _, err := testClient.Project.Get("12310505") + if err != nil { + t.Errorf("Error given: %s", err) + } + if projects == nil { + t.Error("Expected project list. Project list is nil") + return + } + if len(projects.Roles) != 9 { + t.Errorf("Expected 9 roles but got %d", len(projects.Roles)) + } +} + +func TestProjectService_Get_NoProject(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/2/project/99999999" + + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, nil) + }) + + projects, resp, err := testClient.Project.Get("99999999") + if projects != nil { + t.Errorf("Expected nil. Got %+v", projects) + } + + if resp.Status == "404" { + t.Errorf("Expected status 404. Got %s", resp.Status) + } + if err == nil { + t.Errorf("Error given: %s", err) + } +} + +func TestProjectService_GetPermissionScheme_Failure(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/2/project/99999999/permissionscheme" + + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, nil) + }) + + permissionScheme, resp, err := testClient.Project.GetPermissionScheme("99999999") + if permissionScheme != nil { + t.Errorf("Expected nil. Got %+v", permissionScheme) + } + + if resp.Status == "404" { + t.Errorf("Expected status 404. Got %s", resp.Status) + } + if err == nil { + t.Errorf("Error given: %s", err) + } +} + +func TestProjectService_GetPermissionScheme_Success(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/2/project/99999999/permissionscheme" + + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, `{ + "expand": "permissions,user,group,projectRole,field,all", + "id": 10201, + "self": "https://www.example.com/rest/api/2/permissionscheme/10201", + "name": "Project for specific-users", + "description": "Projects that can only see for people belonging to specific-users group" + }`) + }) + + permissionScheme, resp, err := testClient.Project.GetPermissionScheme("99999999") + if permissionScheme.ID != 10201 { + t.Errorf("Expected Permission Scheme ID. Got %+v", permissionScheme) + } + + if resp.Status == "404" { + t.Errorf("Expected status 404. Got %s", resp.Status) + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} diff --git a/onpremise/request.go b/onpremise/request.go new file mode 100644 index 0000000..3336ce7 --- /dev/null +++ b/onpremise/request.go @@ -0,0 +1,123 @@ +package onpremise + +import ( + "context" + "fmt" +) + +// RequestService handles ServiceDesk customer requests for the Jira instance / API. +type RequestService struct { + client *Client +} + +// Request represents a ServiceDesk customer request. +type Request struct { + IssueID string `json:"issueId,omitempty" structs:"issueId,omitempty"` + IssueKey string `json:"issueKey,omitempty" structs:"issueKey,omitempty"` + TypeID string `json:"requestTypeId,omitempty" structs:"requestTypeId,omitempty"` + ServiceDeskID string `json:"serviceDeskId,omitempty" structs:"serviceDeskId,omitempty"` + Reporter *Customer `json:"reporter,omitempty" structs:"reporter,omitempty"` + FieldValues []RequestFieldValue `json:"requestFieldValues,omitempty" structs:"requestFieldValues,omitempty"` + Status *RequestStatus `json:"currentStatus,omitempty" structs:"currentStatus,omitempty"` + Links *SelfLink `json:"_links,omitempty" structs:"_links,omitempty"` + Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"` +} + +// RequestFieldValue is a request field. +type RequestFieldValue struct { + FieldID string `json:"fieldId,omitempty" structs:"fieldId,omitempty"` + Label string `json:"label,omitempty" structs:"label,omitempty"` + Value string `json:"value,omitempty" structs:"value,omitempty"` +} + +// RequestDate is the date format used in requests. +type RequestDate struct { + ISO8601 string `json:"iso8601,omitempty" structs:"iso8601,omitempty"` + Jira string `json:"jira,omitempty" structs:"jira,omitempty"` + Friendly string `json:"friendly,omitempty" structs:"friendly,omitempty"` + Epoch int64 `json:"epoch,omitempty" structs:"epoch,omitempty"` +} + +// RequestStatus is the status for a request. +type RequestStatus struct { + Status string + Category string + Date RequestDate +} + +// RequestComment is a comment for a request. +type RequestComment struct { + ID string `json:"id,omitempty" structs:"id,omitempty"` + Body string `json:"body,omitempty" structs:"body,omitempty"` + Public bool `json:"public" structs:"public"` + Author *Customer `json:"author,omitempty" structs:"author,omitempty"` + Created *RequestDate `json:"created,omitempty" structs:"created,omitempty"` + Links *SelfLink `json:"_links,omitempty" structs:"_links,omitempty"` + Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"` +} + +// CreateWithContext creates a new request. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-post +func (r *RequestService) CreateWithContext(ctx context.Context, requester string, participants []string, request *Request) (*Request, *Response, error) { + apiEndpoint := "rest/servicedeskapi/request" + + payload := struct { + *Request + FieldValues map[string]string `json:"requestFieldValues,omitempty"` + Requester string `json:"raiseOnBehalfOf,omitempty"` + Participants []string `json:"requestParticipants,omitempty"` + }{ + Request: request, + FieldValues: make(map[string]string), + Requester: requester, + Participants: participants, + } + + for _, field := range request.FieldValues { + payload.FieldValues[field.FieldID] = field.Value + } + + req, err := r.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload) + if err != nil { + return nil, nil, err + } + + responseRequest := new(Request) + resp, err := r.client.Do(req, responseRequest) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + + return responseRequest, resp, nil +} + +// Create wraps CreateWithContext using the background context. +func (r *RequestService) Create(requester string, participants []string, request *Request) (*Request, *Response, error) { + return r.CreateWithContext(context.Background(), requester, participants, request) +} + +// CreateCommentWithContext creates a comment on a request. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-comment-post +func (r *RequestService) CreateCommentWithContext(ctx context.Context, issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/servicedeskapi/request/%v/comment", issueIDOrKey) + + req, err := r.client.NewRequestWithContext(ctx, "POST", apiEndpoint, comment) + if err != nil { + return nil, nil, err + } + + responseComment := new(RequestComment) + resp, err := r.client.Do(req, responseComment) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + + return responseComment, resp, nil +} + +// CreateComment wraps CreateCommentWithContext using the background context. +func (r *RequestService) CreateComment(issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) { + return r.CreateCommentWithContext(context.Background(), issueIDOrKey, comment) +} diff --git a/onpremise/request_test.go b/onpremise/request_test.go new file mode 100644 index 0000000..886f7ad --- /dev/null +++ b/onpremise/request_test.go @@ -0,0 +1,199 @@ +package onpremise + +import ( + "encoding/json" + "net/http" + "reflect" + "testing" +) + +func TestRequestService_Create(t *testing.T) { + setup() + defer teardown() + + var ( + wantRequester = "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b" + gotRequester string + + wantParticipants = []string{ + "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + } + gotParticipants []string + ) + + testMux.HandleFunc("/rest/servicedeskapi/request", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/servicedeskapi/request") + + var payload struct { + Requester string `json:"raiseOnBehalfOf,omitempty"` + Participants []string `json:"requestParticipants,omitempty"` + } + + if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { + t.Fatal(err) + } + + gotRequester = payload.Requester + gotParticipants = payload.Participants + + w.Write([]byte(`{ + "_expands": [ + "participant", + "status", + "sla", + "requestType", + "serviceDesk", + "attachment", + "action", + "comment" + ], + "issueId": "107001", + "issueKey": "HELPDESK-1", + "requestTypeId": "25", + "serviceDeskId": "10", + "createdDate": { + "iso8601": "2015-10-08T14:42:00+0700", + "jira": "2015-10-08T14:42:00.000+0700", + "friendly": "Monday 14:42 PM", + "epochMillis": 1444290120000 + }, + "reporter": { + "accountId": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "name": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "key": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "emailAddress": "fred@example.com", + "displayName": "Fred F. User", + "active": true, + "timeZone": "Australia/Sydney", + "_links": { + "jiraRest": "https://your-domain.atlassian.net/rest/api/2/user?username=qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "avatarUrls": { + "48x48": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=48&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2F9bc3b5bcb0db050c6d7660b28a5b86c9%3Fd%3Dmm%26s%3D48%26noRedirect%3Dtrue", + "24x24": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=24&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2F9bc3b5bcb0db050c6d7660b28a5b86c9%3Fd%3Dmm%26s%3D24%26noRedirect%3Dtrue", + "16x16": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=16&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2F9bc3b5bcb0db050c6d7660b28a5b86c9%3Fd%3Dmm%26s%3D16%26noRedirect%3Dtrue", + "32x32": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=32&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2F9bc3b5bcb0db050c6d7660b28a5b86c9%3Fd%3Dmm%26s%3D32%26noRedirect%3Dtrue" + }, + "self": "https://your-domain.atlassian.net/rest/api/2/user?username=qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b" + } + }, + "requestFieldValues": [ + { + "fieldId": "summary", + "label": "What do you need?", + "value": "Request JSD help via REST" + }, + { + "fieldId": "description", + "label": "Why do you need this?", + "value": "I need a new *mouse* for my Mac", + "renderedValue": { + "html": "

I need a new mouse for my Mac

" + } + } + ], + "currentStatus": { + "status": "Waiting for Support", + "statusCategory": "NEW", + "statusDate": { + "iso8601": "2015-10-08T14:01:00+0700", + "jira": "2015-10-08T14:01:00.000+0700", + "friendly": "Today 14:01 PM", + "epochMillis": 1444287660000 + } + }, + "_links": { + "jiraRest": "https://your-domain.atlassian.net/rest/api/2/issue/107001", + "web": "https://your-domain.atlassian.net/servicedesk/customer/portal/10/HELPDESK-1", + "self": "https://your-domain.atlassian.net/rest/servicedeskapi/request/107001", + "agent": "https://your-domain.atlassian.net/browse/HELPDESK-1" + } + }`)) + }) + + request := &Request{ + ServiceDeskID: "10", + TypeID: "25", + FieldValues: []RequestFieldValue{ + { + FieldID: "summary", + Value: "Request JSD help via REST", + }, + { + FieldID: "description", + Value: "I need a new *mouse* for my Mac", + }, + }, + } + + _, _, err := testClient.Request.Create(wantRequester, wantParticipants, request) + if err != nil { + t.Fatal(err) + } + + if wantRequester != gotRequester { + t.Fatalf("want requester: %q, got %q", wantRequester, gotRequester) + } + + if !reflect.DeepEqual(wantParticipants, gotParticipants) { + t.Fatalf("want participants: %v, got %v", wantParticipants, gotParticipants) + } +} + +func TestRequestService_CreateComment(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/rest/servicedeskapi/request/HELPDESK-1/comment", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/servicedeskapi/request/HELPDESK-1/comment") + + w.Write([]byte(`{ + "_expands": [ + "attachment", + "renderedBody" + ], + "id": "1000", + "body": "Hello there", + "public": true, + "author": { + "accountId": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "name": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "key": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "emailAddress": "fred@example.com", + "displayName": "Fred F. User", + "active": true, + "timeZone": "Australia/Sydney", + "_links": { + "jiraRest": "https://your-domain.atlassian.net/rest/api/2/user?username=qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "avatarUrls": { + "48x48": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=48&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2F9bc3b5bcb0db050c6d7660b28a5b86c9%3Fd%3Dmm%26s%3D48%26noRedirect%3Dtrue", + "24x24": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=24&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2F9bc3b5bcb0db050c6d7660b28a5b86c9%3Fd%3Dmm%26s%3D24%26noRedirect%3Dtrue", + "16x16": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=16&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2F9bc3b5bcb0db050c6d7660b28a5b86c9%3Fd%3Dmm%26s%3D16%26noRedirect%3Dtrue", + "32x32": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=32&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2F9bc3b5bcb0db050c6d7660b28a5b86c9%3Fd%3Dmm%26s%3D32%26noRedirect%3Dtrue" + }, + "self": "https://your-domain.atlassian.net/rest/api/2/user?username=qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b" + } + }, + "created": { + "iso8601": "2015-10-09T10:22:00+0700", + "jira": "2015-10-09T10:22:00.000+0700", + "friendly": "Today 10:22 AM", + "epochMillis": 1444360920000 + }, + "_links": { + "self": "https://your-domain.atlassian.net/rest/servicedeskapi/request/2000/comment/1000" + } + }`)) + }) + + comment := &RequestComment{ + Body: "Hello there", + Public: true, + } + + _, _, err := testClient.Request.CreateComment("HELPDESK-1", comment) + if err != nil { + t.Fatal(err) + } +} diff --git a/onpremise/resolution.go b/onpremise/resolution.go new file mode 100644 index 0000000..76db3d6 --- /dev/null +++ b/onpremise/resolution.go @@ -0,0 +1,42 @@ +package onpremise + +import "context" + +// ResolutionService handles resolutions for the Jira instance / API. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Resolution +type ResolutionService struct { + client *Client +} + +// Resolution represents a resolution of a Jira issue. +// Typical types are "Fixed", "Suspended", "Won't Fix", ... +type Resolution struct { + Self string `json:"self" structs:"self"` + ID string `json:"id" structs:"id"` + Description string `json:"description" structs:"description"` + Name string `json:"name" structs:"name"` +} + +// GetListWithContext gets all resolutions from Jira +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-resolution-get +func (s *ResolutionService) GetListWithContext(ctx context.Context) ([]Resolution, *Response, error) { + apiEndpoint := "rest/api/2/resolution" + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + resolutionList := []Resolution{} + resp, err := s.client.Do(req, &resolutionList) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return resolutionList, resp, nil +} + +// GetList wraps GetListWithContext using the background context. +func (s *ResolutionService) GetList() ([]Resolution, *Response, error) { + return s.GetListWithContext(context.Background()) +} diff --git a/onpremise/resolution_test.go b/onpremise/resolution_test.go new file mode 100644 index 0000000..70629ca --- /dev/null +++ b/onpremise/resolution_test.go @@ -0,0 +1,32 @@ +package onpremise + +import ( + "fmt" + "net/http" + "os" + "testing" +) + +func TestResolutionService_GetList(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/2/resolution" + + raw, err := os.ReadFile("../testing/mock-data/all_resolutions.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, string(raw)) + }) + + resolution, _, err := testClient.Resolution.GetList() + if resolution == nil { + t.Error("Expected resolution list. Resolution list is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} diff --git a/onpremise/role.go b/onpremise/role.go new file mode 100644 index 0000000..01d94bf --- /dev/null +++ b/onpremise/role.go @@ -0,0 +1,87 @@ +package onpremise + +import ( + "context" + "fmt" +) + +// RoleService handles roles for the Jira instance / API. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Role +type RoleService struct { + client *Client +} + +// Role represents a Jira product role +type Role struct { + Self string `json:"self" structs:"self"` + Name string `json:"name" structs:"name"` + ID int `json:"id" structs:"id"` + Description string `json:"description" structs:"description"` + Actors []*Actor `json:"actors" structs:"actors"` +} + +// Actor represents a Jira actor +type Actor struct { + ID int `json:"id" structs:"id"` + DisplayName string `json:"displayName" structs:"displayName"` + Type string `json:"type" structs:"type"` + Name string `json:"name" structs:"name"` + AvatarURL string `json:"avatarUrl" structs:"avatarUrl"` + ActorUser *ActorUser `json:"actorUser" structs:"actoruser"` +} + +// ActorUser contains the account id of the actor/user +type ActorUser struct { + AccountID string `json:"accountId" structs:"accountId"` +} + +// GetListWithContext returns a list of all available project roles +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-get +func (s *RoleService) GetListWithContext(ctx context.Context) (*[]Role, *Response, error) { + apiEndpoint := "rest/api/3/role" + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + roles := new([]Role) + resp, err := s.client.Do(req, roles) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + return roles, resp, err +} + +// GetList wraps GetListWithContext using the background context. +func (s *RoleService) GetList() (*[]Role, *Response, error) { + return s.GetListWithContext(context.Background()) +} + +// GetWithContext retreives a single Role from Jira +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-id-get +func (s *RoleService) GetWithContext(ctx context.Context, roleID int) (*Role, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/3/role/%d", roleID) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + role := new(Role) + resp, err := s.client.Do(req, role) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + if role.Self == "" { + return nil, resp, fmt.Errorf("no role with ID %d found", roleID) + } + + return role, resp, err +} + +// Get wraps GetWithContext using the background context. +func (s *RoleService) Get(roleID int) (*Role, *Response, error) { + return s.GetWithContext(context.Background(), roleID) +} diff --git a/onpremise/role_test.go b/onpremise/role_test.go new file mode 100644 index 0000000..524e9c3 --- /dev/null +++ b/onpremise/role_test.go @@ -0,0 +1,107 @@ +package onpremise + +import ( + "fmt" + "net/http" + "os" + "testing" +) + +func TestRoleService_GetList_NoList(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/3/role" + + raw, err := os.ReadFile("../testing/mock-data/no_roles.json") + if err != nil { + t.Error(err.Error()) + } + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + roles, _, err := testClient.Role.GetList() + if roles != nil { + t.Errorf("Expected role list has %d entries but should be nil", len(*roles)) + } + if err == nil { + t.Errorf("No error given") + } +} + +func TestRoleService_GetList(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/3/role" + + raw, err := os.ReadFile("../testing/mock-data/all_roles.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + roles, _, err := testClient.Role.GetList() + if err != nil { + t.Errorf("Error given: %v", err) + } + if roles == nil { + t.Error("Expected role list. Role list is nil") + return + } + if len(*roles) != 2 { + t.Errorf("Expected %d roles but got %d", 2, len(*roles)) + } +} + +func TestRoleService_Get_NoRole(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/3/role/99999" + raw, err := os.ReadFile("../testing/mock-data/no_role.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { + testMethod(t, request, "GET") + testRequestURL(t, request, testAPIEdpoint) + fmt.Fprint(writer, string(raw)) + }) + + role, _, err := testClient.Role.Get(99999) + if role != nil { + t.Errorf("Expected nil, got role %v", role) + } + if err == nil { + t.Errorf("No error given") + } +} + +func TestRoleService_Get(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/3/role/10002" + raw, err := os.ReadFile("../testing/mock-data/role.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { + testMethod(t, request, "GET") + testRequestURL(t, request, testAPIEdpoint) + fmt.Fprint(writer, string(raw)) + }) + + role, _, err := testClient.Role.Get(10002) + if role == nil { + t.Errorf("Expected Role, got nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} diff --git a/onpremise/servicedesk.go b/onpremise/servicedesk.go new file mode 100644 index 0000000..d935cc1 --- /dev/null +++ b/onpremise/servicedesk.go @@ -0,0 +1,226 @@ +package onpremise + +import ( + "context" + "encoding/json" + "fmt" + "io" + + "github.com/google/go-querystring/query" +) + +// ServiceDeskService handles ServiceDesk for the Jira instance / API. +type ServiceDeskService struct { + client *Client +} + +// ServiceDeskOrganizationDTO is a DTO for ServiceDesk organizations +type ServiceDeskOrganizationDTO struct { + OrganizationID int `json:"organizationId,omitempty" structs:"organizationId,omitempty"` +} + +// GetOrganizationsWithContext returns a list of +// all organizations associated with a service desk. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-get +func (s *ServiceDeskService) GetOrganizationsWithContext(ctx context.Context, serviceDeskID interface{}, start int, limit int, accountID string) (*PagedDTO, *Response, error) { + apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization?start=%d&limit=%d", serviceDeskID, start, limit) + if accountID != "" { + apiEndPoint += fmt.Sprintf("&accountId=%s", accountID) + } + + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req.Header.Set("Accept", "application/json") + + if err != nil { + return nil, nil, err + } + + orgs := new(PagedDTO) + resp, err := s.client.Do(req, &orgs) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return orgs, resp, nil +} + +// GetOrganizations wraps GetOrganizationsWithContext using the background context. +func (s *ServiceDeskService) GetOrganizations(serviceDeskID interface{}, start int, limit int, accountID string) (*PagedDTO, *Response, error) { + return s.GetOrganizationsWithContext(context.Background(), serviceDeskID, start, limit, accountID) +} + +// AddOrganizationWithContext adds an organization to +// a service desk. If the organization ID is already +// associated with the service desk, no change is made +// and the resource returns a 204 success code. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-post +// Caller must close resp.Body +func (s *ServiceDeskService) AddOrganizationWithContext(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) { + apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization", serviceDeskID) + + organization := ServiceDeskOrganizationDTO{ + OrganizationID: organizationID, + } + + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, organization) + + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return resp, jerr + } + + return resp, nil +} + +// AddOrganization wraps AddOrganizationWithContext using the background context. +// Caller must close resp.Body +func (s *ServiceDeskService) AddOrganization(serviceDeskID interface{}, organizationID int) (*Response, error) { + return s.AddOrganizationWithContext(context.Background(), serviceDeskID, organizationID) +} + +// RemoveOrganizationWithContext removes an organization +// from a service desk. If the organization ID does not +// match an organization associated with the service desk, +// no change is made and the resource returns a 204 success code. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-delete +// Caller must close resp.Body +func (s *ServiceDeskService) RemoveOrganizationWithContext(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) { + apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization", serviceDeskID) + + organization := ServiceDeskOrganizationDTO{ + OrganizationID: organizationID, + } + + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, organization) + + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return resp, jerr + } + + return resp, nil +} + +// RemoveOrganization wraps RemoveOrganizationWithContext using the background context. +// Caller must close resp.Body +func (s *ServiceDeskService) RemoveOrganization(serviceDeskID interface{}, organizationID int) (*Response, error) { + return s.RemoveOrganizationWithContext(context.Background(), serviceDeskID, organizationID) +} + +// AddCustomersWithContext adds customers to the given service desk. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-post +func (s *ServiceDeskService) AddCustomersWithContext(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) { + apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) + + payload := struct { + AccountIDs []string `json:"accountIds"` + }{ + AccountIDs: acountIDs, + } + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, NewJiraError(resp, err) + } + + defer resp.Body.Close() + _, _ = io.Copy(io.Discard, resp.Body) + + return resp, nil +} + +// AddCustomers wraps AddCustomersWithContext using the background context. +func (s *ServiceDeskService) AddCustomers(serviceDeskID interface{}, acountIDs ...string) (*Response, error) { + return s.AddCustomersWithContext(context.Background(), serviceDeskID, acountIDs...) +} + +// RemoveCustomersWithContext removes customers to the given service desk. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-delete +func (s *ServiceDeskService) RemoveCustomersWithContext(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) { + apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) + + payload := struct { + AccountIDs []string `json:"accountIDs"` + }{ + AccountIDs: acountIDs, + } + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, payload) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, NewJiraError(resp, err) + } + + defer resp.Body.Close() + _, _ = io.Copy(io.Discard, resp.Body) + + return resp, nil +} + +// RemoveCustomers wraps RemoveCustomersWithContext using the background context. +func (s *ServiceDeskService) RemoveCustomers(serviceDeskID interface{}, acountIDs ...string) (*Response, error) { + return s.RemoveCustomersWithContext(context.Background(), serviceDeskID, acountIDs...) +} + +// ListCustomersWithContext lists customers for a ServiceDesk. +// +// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-get +func (s *ServiceDeskService) ListCustomersWithContext(ctx context.Context, serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + // this is an experiemntal endpoint + req.Header.Set("X-ExperimentalApi", "opt-in") + + if options != nil { + q, err := query.Values(options) + if err != nil { + return nil, nil, err + } + req.URL.RawQuery = q.Encode() + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + defer resp.Body.Close() + + customerList := new(CustomerList) + if err := json.NewDecoder(resp.Body).Decode(customerList); err != nil { + return nil, resp, fmt.Errorf("could not unmarshall the data into struct") + } + + return customerList, resp, nil +} + +// ListCustomers wraps ListCustomersWithContext using the background context. +func (s *ServiceDeskService) ListCustomers(serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) { + return s.ListCustomersWithContext(context.Background(), serviceDeskID, options) +} diff --git a/onpremise/servicedesk_test.go b/onpremise/servicedesk_test.go new file mode 100644 index 0000000..5828e12 --- /dev/null +++ b/onpremise/servicedesk_test.go @@ -0,0 +1,430 @@ +package onpremise + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "sort" + "strconv" + "testing" +) + +func TestServiceDeskService_GetOrganizations(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/servicedesk/10001/organization", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/10001/organization") + + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{ + "_expands": [], + "size": 3, + "start": 3, + "limit": 3, + "isLastPage": false, + "_links": { + "base": "https://your-domain.atlassian.net/rest/servicedeskapi", + "context": "context", + "next": "https://your-domain.atlassian.net/rest/servicedeskapi/servicedesk/10001/organization?start=6&limit=3", + "prev": "https://your-domain.atlassian.net/rest/servicedeskapi/servicedesk/10001/organization?start=0&limit=3" + }, + "values": [ + { + "id": "1", + "name": "Charlie Cakes Franchises", + "_links": { + "self": "https://your-domain.atlassian.net/rest/servicedeskapi/organization/1" + } + }, + { + "id": "2", + "name": "Atlas Coffee Co", + "_links": { + "self": "https://your-domain.atlassian.net/rest/servicedeskapi/organization/2" + } + }, + { + "id": "3", + "name": "The Adjustment Bureau", + "_links": { + "self": "https://your-domain.atlassian.net/rest/servicedeskapi/organization/3" + } + } + ] + }`) + }) + + orgs, _, err := testClient.ServiceDesk.GetOrganizations(10001, 3, 3, "") + + if orgs == nil { + t.Error("Expected Organizations. Result is nil") + } else if orgs.Size != 3 { + t.Errorf("Expected size to be 3, but got %d", orgs.Size) + } + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestServiceDeskService_AddOrganizations(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/servicedesk/10001/organization", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/10001/organization") + + w.WriteHeader(http.StatusNoContent) + }) + + _, err := testClient.ServiceDesk.AddOrganization(10001, 1) + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestServiceDeskService_RemoveOrganizations(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/servicedesk/10001/organization", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/10001/organization") + + w.WriteHeader(http.StatusNoContent) + }) + + _, err := testClient.ServiceDesk.RemoveOrganization(10001, 1) + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestServiceDeskServiceStringServiceDeskID_GetOrganizations(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/servicedesk/TEST/organization", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/TEST/organization") + + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{ + "_expands": [], + "size": 3, + "start": 3, + "limit": 3, + "isLastPage": false, + "_links": { + "base": "https://your-domain.atlassian.net/rest/servicedeskapi", + "context": "context", + "next": "https://your-domain.atlassian.net/rest/servicedeskapi/servicedesk/TEST/organization?start=6&limit=3", + "prev": "https://your-domain.atlassian.net/rest/servicedeskapi/servicedesk/TEST/organization?start=0&limit=3" + }, + "values": [ + { + "id": "1", + "name": "Charlie Cakes Franchises", + "_links": { + "self": "https://your-domain.atlassian.net/rest/servicedeskapi/organization/1" + } + }, + { + "id": "2", + "name": "Atlas Coffee Co", + "_links": { + "self": "https://your-domain.atlassian.net/rest/servicedeskapi/organization/2" + } + }, + { + "id": "3", + "name": "The Adjustment Bureau", + "_links": { + "self": "https://your-domain.atlassian.net/rest/servicedeskapi/organization/3" + } + } + ] + }`) + }) + + orgs, _, err := testClient.ServiceDesk.GetOrganizations("TEST", 3, 3, "") + + if orgs == nil { + t.Error("Expected Organizations. Result is nil") + } else if orgs.Size != 3 { + t.Errorf("Expected size to be 3, but got %d", orgs.Size) + } + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestServiceDeskServiceStringServiceDeskID_AddOrganizations(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/servicedesk/TEST/organization", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/TEST/organization") + + w.WriteHeader(http.StatusNoContent) + }) + + _, err := testClient.ServiceDesk.AddOrganization("TEST", 1) + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestServiceDeskServiceStringServiceDeskID_RemoveOrganizations(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/servicedeskapi/servicedesk/TEST/organization", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/TEST/organization") + + w.WriteHeader(http.StatusNoContent) + }) + + _, err := testClient.ServiceDesk.RemoveOrganization("TEST", 1) + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestServiceDeskService_AddCustomers(t *testing.T) { + tests := []struct { + name string + serviceDeskID interface{} + }{ + { + name: "string service desk id", + serviceDeskID: "10000", + }, + { + name: "int service desk id", + serviceDeskID: 10000, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + setup() + defer teardown() + + var ( + wantAccountIDs = []string{ + "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3a01db05e2a66fa80bd", + "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + } + gotAccountIDs []string + ) + + testMux.HandleFunc(fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID), func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID)) + + var payload struct { + AccountIDs []string `json:"accountIds"` + } + + if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { + t.Fatal(err) + } + + gotAccountIDs = append(gotAccountIDs, payload.AccountIDs...) + + w.WriteHeader(http.StatusNoContent) + }) + + _, err := testClient.ServiceDesk.AddCustomers(test.serviceDeskID, wantAccountIDs...) + + if err != nil { + t.Errorf("Error given: %s", err) + } + + if want, got := len(wantAccountIDs), len(gotAccountIDs); want != got { + t.Fatalf("want account id length: %d, got %d", want, got) + } + + sort.Strings(wantAccountIDs) + sort.Strings(gotAccountIDs) + + if !reflect.DeepEqual(wantAccountIDs, gotAccountIDs) { + t.Fatalf("want account ids: %v, got %v", wantAccountIDs, gotAccountIDs) + } + }) + } +} + +func TestServiceDeskService_RemoveCustomers(t *testing.T) { + tests := []struct { + name string + serviceDeskID interface{} + }{ + { + name: "string service desk id", + serviceDeskID: "10000", + }, + { + name: "int service desk id", + serviceDeskID: 10000, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + setup() + defer teardown() + + var ( + wantAccountIDs = []string{ + "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3a01db05e2a66fa80bd", + "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + } + gotAccountIDs []string + ) + + testMux.HandleFunc(fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID), func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID)) + + var payload struct { + AccountIDs []string `json:"accountIds"` + } + + if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { + t.Fatal(err) + } + + gotAccountIDs = append(gotAccountIDs, payload.AccountIDs...) + + w.WriteHeader(http.StatusNoContent) + }) + + _, err := testClient.ServiceDesk.RemoveCustomers(test.serviceDeskID, wantAccountIDs...) + + if err != nil { + t.Errorf("Error given: %s", err) + } + + if want, got := len(wantAccountIDs), len(gotAccountIDs); want != got { + t.Fatalf("want account id length: %d, got %d", want, got) + } + + sort.Strings(wantAccountIDs) + sort.Strings(gotAccountIDs) + + if !reflect.DeepEqual(wantAccountIDs, gotAccountIDs) { + t.Fatalf("want account ids: %v, got %v", wantAccountIDs, gotAccountIDs) + } + }) + } +} + +func TestServiceDeskService_ListCustomers(t *testing.T) { + tests := []struct { + name string + serviceDeskID interface{} + }{ + { + name: "string service desk id", + serviceDeskID: "10000", + }, + { + name: "int service desk id", + serviceDeskID: 10000, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + setup() + defer teardown() + + var ( + email = "fred@example.com" + wantOptions = &CustomerListOptions{ + Query: email, + Start: 1, + Limit: 10, + } + + gotOptions = new(CustomerListOptions) + ) + + testMux.HandleFunc(fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID), func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID)) + + qs := r.URL.Query() + gotOptions.Query = qs.Get("query") + if start := qs.Get("start"); start != "" { + gotOptions.Start, _ = strconv.Atoi(start) + } + if limit := qs.Get("limit"); limit != "" { + gotOptions.Limit, _ = strconv.Atoi(limit) + } + + w.Write([]byte(`{ + "_expands": [], + "size": 1, + "start": 1, + "limit": 1, + "isLastPage": false, + "_links": { + "base": "https://your-domain.atlassian.net/rest/servicedeskapi", + "context": "context", + "next": "https://your-domain.atlassian.net/rest/servicedeskapi/servicedesk/1/customer?start=2&limit=1", + "prev": "https://your-domain.atlassian.net/rest/servicedeskapi/servicedesk/1/customer?start=0&limit=1" + }, + "values": [ + { + "accountId": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "name": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "key": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "emailAddress": "fred@example.com", + "displayName": "Fred F. User", + "active": true, + "timeZone": "Australia/Sydney", + "_links": { + "jiraRest": "https://your-domain.atlassian.net/rest/api/2/user?username=qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b", + "avatarUrls": { + "48x48": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=48&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2F9bc3b5bcb0db050c6d7660b28a5b86c9%3Fd%3Dmm%26s%3D48%26noRedirect%3Dtrue", + "24x24": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=24&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2F9bc3b5bcb0db050c6d7660b28a5b86c9%3Fd%3Dmm%26s%3D24%26noRedirect%3Dtrue", + "16x16": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=16&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2F9bc3b5bcb0db050c6d7660b28a5b86c9%3Fd%3Dmm%26s%3D16%26noRedirect%3Dtrue", + "32x32": "https://avatar-cdn.atlassian.com/9bc3b5bcb0db050c6d7660b28a5b86c9?s=32&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2F9bc3b5bcb0db050c6d7660b28a5b86c9%3Fd%3Dmm%26s%3D32%26noRedirect%3Dtrue" + }, + "self": "https://your-domain.atlassian.net/rest/api/2/user?username=qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b" + } + } + ] + }`)) + }) + + customerList, _, err := testClient.ServiceDesk.ListCustomers(test.serviceDeskID, wantOptions) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(wantOptions, gotOptions) { + t.Fatalf("want options: %#v, got %#v", wantOptions, gotOptions) + } + + if want, got := 1, len(customerList.Values); want != got { + t.Fatalf("want customer count: %d, got %d", want, got) + } + + if want, got := email, customerList.Values[0].EmailAddress; want != got { + t.Fatalf("want customer email: %q, got %q", want, got) + } + }) + } +} diff --git a/onpremise/sprint.go b/onpremise/sprint.go new file mode 100644 index 0000000..d7560ca --- /dev/null +++ b/onpremise/sprint.go @@ -0,0 +1,125 @@ +package onpremise + +import ( + "context" + "fmt" + + "github.com/google/go-querystring/query" +) + +// SprintService handles sprints in Jira Agile API. +// See https://docs.atlassian.com/jira-software/REST/cloud/ +type SprintService struct { + client *Client +} + +// IssuesWrapper represents a wrapper struct for moving issues to sprint +type IssuesWrapper struct { + Issues []string `json:"issues"` +} + +// IssuesInSprintResult represents a wrapper struct for search result +type IssuesInSprintResult struct { + Issues []Issue `json:"issues"` +} + +// MoveIssuesToSprintWithContext moves issues to a sprint, for a given sprint Id. +// Issues can only be moved to open or active sprints. +// The maximum number of issues that can be moved in one operation is 50. +// +// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-moveIssuesToSprint +// Caller must close resp.Body +func (s *SprintService) MoveIssuesToSprintWithContext(ctx context.Context, sprintID int, issueIDs []string) (*Response, error) { + apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) + + payload := IssuesWrapper{Issues: issueIDs} + + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload) + + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + err = NewJiraError(resp, err) + } + return resp, err +} + +// MoveIssuesToSprint wraps MoveIssuesToSprintWithContext using the background context. +// Caller must close resp.Body +func (s *SprintService) MoveIssuesToSprint(sprintID int, issueIDs []string) (*Response, error) { + return s.MoveIssuesToSprintWithContext(context.Background(), sprintID, issueIDs) +} + +// GetIssuesForSprintWithContext returns all issues in a sprint, for a given sprint Id. +// This only includes issues that the user has permission to view. +// By default, the returned issues are ordered by rank. +// +// Jira API Docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-getIssuesForSprint +func (s *SprintService) GetIssuesForSprintWithContext(ctx context.Context, sprintID int) ([]Issue, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) + + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + + if err != nil { + return nil, nil, err + } + + result := new(IssuesInSprintResult) + resp, err := s.client.Do(req, result) + if err != nil { + err = NewJiraError(resp, err) + } + + return result.Issues, resp, err +} + +// GetIssuesForSprint wraps GetIssuesForSprintWithContext using the background context. +func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, error) { + return s.GetIssuesForSprintWithContext(context.Background(), sprintID) +} + +// GetIssueWithContext returns a full representation of the issue for the given issue key. +// Jira will attempt to identify the issue by the issueIdOrKey path parameter. +// This can be an issue id, or an issue key. +// If the issue cannot be found via an exact match, Jira will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved. +// +// # The given options will be appended to the query string +// +// Jira API docs: https://docs.atlassian.com/jira-software/REST/7.3.1/#agile/1.0/issue-getIssue +// +// TODO: create agile service for holding all agile apis' implementation +func (s *SprintService) GetIssueWithContext(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/agile/1.0/issue/%s", issueID) + + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + + if err != nil { + return nil, nil, err + } + + if options != nil { + q, err := query.Values(options) + if err != nil { + return nil, nil, err + } + req.URL.RawQuery = q.Encode() + } + + issue := new(Issue) + resp, err := s.client.Do(req, issue) + + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + return issue, resp, nil +} + +// GetIssue wraps GetIssueWithContext using the background context. +func (s *SprintService) GetIssue(issueID string, options *GetQueryOptions) (*Issue, *Response, error) { + return s.GetIssueWithContext(context.Background(), issueID, options) +} diff --git a/onpremise/sprint_test.go b/onpremise/sprint_test.go new file mode 100644 index 0000000..25e743a --- /dev/null +++ b/onpremise/sprint_test.go @@ -0,0 +1,116 @@ +package onpremise + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "reflect" + "testing" +) + +func TestSprintService_MoveIssuesToSprint(t *testing.T) { + setup() + defer teardown() + + testAPIEndpoint := "/rest/agile/1.0/sprint/123/issue" + + issuesToMove := []string{"KEY-1", "KEY-2"} + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, testAPIEndpoint) + + decoder := json.NewDecoder(r.Body) + var payload IssuesWrapper + err := decoder.Decode(&payload) + if err != nil { + t.Errorf("Got error: %v", err) + } + + if payload.Issues[0] != issuesToMove[0] { + t.Errorf("Expected %s to be in payload, got %s instead", issuesToMove[0], payload.Issues[0]) + } + }) + _, err := testClient.Sprint.MoveIssuesToSprint(123, issuesToMove) + + if err != nil { + t.Errorf("Got error: %v", err) + } +} + +func TestSprintService_GetIssuesForSprint(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/agile/1.0/sprint/123/issue" + + raw, err := os.ReadFile("../testing/mock-data/issues_in_sprint.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, string(raw)) + }) + + issues, _, err := testClient.Sprint.GetIssuesForSprint(123) + if err != nil { + t.Errorf("Error given: %v", err) + } + if issues == nil { + t.Error("Expected issues in sprint list. Issues list is nil") + } + if len(issues) != 1 { + t.Errorf("Expect there to be 1 issue in the sprint, found %v", len(issues)) + } + +} + +func TestSprintService_GetIssue(t *testing.T) { + setup() + defer teardown() + + testAPIEndpoint := "/rest/agile/1.0/issue/10002" + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"sprint": {"id": 37,"self": "http://www.example.com/jira/rest/agile/1.0/sprint/13", "state": "future", "name": "sprint 2"}, "epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) + }) + + issue, _, err := testClient.Sprint.GetIssue("10002", nil) + if err != nil { + t.Errorf("Error given: %s", err) + } + if issue == nil { + t.Errorf("Expected issue. Issue is nil %v", err) + return + } + if !reflect.DeepEqual(issue.Fields.Labels, []string{"test"}) { + t.Error("Expected labels for the returned issue") + } + if len(issue.Fields.Comments.Comments) != 1 { + t.Errorf("Expected one comment, %v found", len(issue.Fields.Comments.Comments)) + } + if len(issue.Names) != 10 { + t.Errorf("Expected 10 names, %v found", len(issue.Names)) + } + if !reflect.DeepEqual(issue.Names, map[string]string{ + "watcher": "watcher", + "attachment": "attachment", + "sub-tasks": "sub-tasks", + "description": "description", + "project": "project", + "comment": "comment", + "issuelinks": "issuelinks", + "worklog": "worklog", + "updated": "updated", + "timetracking": "timetracking", + }) { + t.Error("Expected names for the returned issue") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} diff --git a/onpremise/status.go b/onpremise/status.go new file mode 100644 index 0000000..93b4ca9 --- /dev/null +++ b/onpremise/status.go @@ -0,0 +1,47 @@ +package onpremise + +import "context" + +// StatusService handles staties for the Jira instance / API. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Workflow-statuses +type StatusService struct { + client *Client +} + +// Status represents the current status of a Jira issue. +// Typical status are "Open", "In Progress", "Closed", ... +// Status can be user defined in every Jira instance. +type Status struct { + Self string `json:"self" structs:"self"` + Description string `json:"description" structs:"description"` + IconURL string `json:"iconUrl" structs:"iconUrl"` + Name string `json:"name" structs:"name"` + ID string `json:"id" structs:"id"` + StatusCategory StatusCategory `json:"statusCategory" structs:"statusCategory"` +} + +// GetAllStatusesWithContext returns a list of all statuses associated with workflows. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-status-get +func (s *StatusService) GetAllStatusesWithContext(ctx context.Context) ([]Status, *Response, error) { + apiEndpoint := "rest/api/2/status" + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + + if err != nil { + return nil, nil, err + } + + statusList := []Status{} + resp, err := s.client.Do(req, &statusList) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + + return statusList, resp, nil +} + +// GetAllStatuses wraps GetAllStatusesWithContext using the background context. +func (s *StatusService) GetAllStatuses() ([]Status, *Response, error) { + return s.GetAllStatusesWithContext(context.Background()) +} diff --git a/onpremise/status_test.go b/onpremise/status_test.go new file mode 100644 index 0000000..0f9475f --- /dev/null +++ b/onpremise/status_test.go @@ -0,0 +1,35 @@ +package onpremise + +import ( + "fmt" + "net/http" + "os" + "testing" +) + +func TestStatusService_GetAllStatuses(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/2/status" + + raw, err := os.ReadFile("../testing/mock-data/all_statuses.json") + if err != nil { + t.Error(err.Error()) + } + + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, string(raw)) + }) + + statusList, _, err := testClient.Status.GetAllStatuses() + + if statusList == nil { + t.Error("Expected statusList. statusList is nill") + } + + if err != nil { + t.Errorf("Error given: %s", err) + } +} diff --git a/onpremise/statuscategory.go b/onpremise/statuscategory.go new file mode 100644 index 0000000..8918fbe --- /dev/null +++ b/onpremise/statuscategory.go @@ -0,0 +1,51 @@ +package onpremise + +import "context" + +// StatusCategoryService handles status categories for the Jira instance / API. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Statuscategory +type StatusCategoryService struct { + client *Client +} + +// StatusCategory represents the category a status belongs to. +// Those categories can be user defined in every Jira instance. +type StatusCategory struct { + Self string `json:"self" structs:"self"` + ID int `json:"id" structs:"id"` + Name string `json:"name" structs:"name"` + Key string `json:"key" structs:"key"` + ColorName string `json:"colorName" structs:"colorName"` +} + +// These constants are the keys of the default Jira status categories +const ( + StatusCategoryComplete = "done" + StatusCategoryInProgress = "indeterminate" + StatusCategoryToDo = "new" + StatusCategoryUndefined = "undefined" +) + +// GetListWithContext gets all status categories from Jira +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get +func (s *StatusCategoryService) GetListWithContext(ctx context.Context) ([]StatusCategory, *Response, error) { + apiEndpoint := "rest/api/2/statuscategory" + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + statusCategoryList := []StatusCategory{} + resp, err := s.client.Do(req, &statusCategoryList) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return statusCategoryList, resp, nil +} + +// GetList wraps GetListWithContext using the background context. +func (s *StatusCategoryService) GetList() ([]StatusCategory, *Response, error) { + return s.GetListWithContext(context.Background()) +} diff --git a/onpremise/statuscategory_test.go b/onpremise/statuscategory_test.go new file mode 100644 index 0000000..e06e717 --- /dev/null +++ b/onpremise/statuscategory_test.go @@ -0,0 +1,32 @@ +package onpremise + +import ( + "fmt" + "net/http" + "os" + "testing" +) + +func TestStatusCategoryService_GetList(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/api/2/statuscategory" + + raw, err := os.ReadFile("../testing/mock-data/all_statuscategories.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, string(raw)) + }) + + statusCategory, _, err := testClient.StatusCategory.GetList() + if statusCategory == nil { + t.Error("Expected statusCategory list. StatusCategory list is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} diff --git a/onpremise/types.go b/onpremise/types.go new file mode 100644 index 0000000..7eec476 --- /dev/null +++ b/onpremise/types.go @@ -0,0 +1,9 @@ +package onpremise + +// Bool is a helper routine that allocates a new bool value +// to store v and returns a pointer to it. +func Bool(v bool) *bool { + p := new(bool) + *p = v + return p +} diff --git a/onpremise/user.go b/onpremise/user.go new file mode 100644 index 0000000..bec5009 --- /dev/null +++ b/onpremise/user.go @@ -0,0 +1,294 @@ +package onpremise + +import ( + "context" + "encoding/json" + "fmt" + "io" +) + +// UserService handles users for the Jira instance / API. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Users +type UserService struct { + client *Client +} + +// User represents a Jira user. +type User struct { + Self string `json:"self,omitempty" structs:"self,omitempty"` + AccountID string `json:"accountId,omitempty" structs:"accountId,omitempty"` + AccountType string `json:"accountType,omitempty" structs:"accountType,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + Key string `json:"key,omitempty" structs:"key,omitempty"` + Password string `json:"-"` + EmailAddress string `json:"emailAddress,omitempty" structs:"emailAddress,omitempty"` + AvatarUrls AvatarUrls `json:"avatarUrls,omitempty" structs:"avatarUrls,omitempty"` + DisplayName string `json:"displayName,omitempty" structs:"displayName,omitempty"` + Active bool `json:"active,omitempty" structs:"active,omitempty"` + TimeZone string `json:"timeZone,omitempty" structs:"timeZone,omitempty"` + Locale string `json:"locale,omitempty" structs:"locale,omitempty"` + ApplicationKeys []string `json:"applicationKeys,omitempty" structs:"applicationKeys,omitempty"` +} + +// UserGroup represents the group list +type UserGroup struct { + Self string `json:"self,omitempty" structs:"self,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` +} + +type userSearchParam struct { + name string + value string +} + +type userSearch []userSearchParam + +type userSearchF func(userSearch) userSearch + +// GetWithContext gets user info from Jira using its Account Id +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-get +func (s *UserService) GetWithContext(ctx context.Context, accountId string) (*User, *Response, error) { + apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + user := new(User) + resp, err := s.client.Do(req, user) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return user, resp, nil +} + +// Get wraps GetWithContext using the background context. +func (s *UserService) Get(accountId string) (*User, *Response, error) { + return s.GetWithContext(context.Background(), accountId) +} + +// GetByAccountIDWithContext gets user info from Jira +// Searching by another parameter that is not accountId is deprecated, +// but this method is kept for backwards compatibility +// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser +func (s *UserService) GetByAccountIDWithContext(ctx context.Context, accountID string) (*User, *Response, error) { + apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountID) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + user := new(User) + resp, err := s.client.Do(req, user) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return user, resp, nil +} + +// GetByAccountID wraps GetByAccountIDWithContext using the background context. +func (s *UserService) GetByAccountID(accountID string) (*User, *Response, error) { + return s.GetByAccountIDWithContext(context.Background(), accountID) +} + +// CreateWithContext creates an user in Jira. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-createUser +func (s *UserService) CreateWithContext(ctx context.Context, user *User) (*User, *Response, error) { + apiEndpoint := "/rest/api/2/user" + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, user) + if err != nil { + return nil, nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return nil, resp, err + } + + responseUser := new(User) + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + e := fmt.Errorf("could not read the returned data") + return nil, resp, NewJiraError(resp, e) + } + err = json.Unmarshal(data, responseUser) + if err != nil { + e := fmt.Errorf("could not unmarshall the data into struct") + return nil, resp, NewJiraError(resp, e) + } + return responseUser, resp, nil +} + +// Create wraps CreateWithContext using the background context. +func (s *UserService) Create(user *User) (*User, *Response, error) { + return s.CreateWithContext(context.Background(), user) +} + +// DeleteWithContext deletes an user from Jira. +// Returns http.StatusNoContent on success. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-delete +// Caller must close resp.Body +func (s *UserService) DeleteWithContext(ctx context.Context, accountId string) (*Response, error) { + apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) + req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, NewJiraError(resp, err) + } + return resp, nil +} + +// Delete wraps DeleteWithContext using the background context. +// Caller must close resp.Body +func (s *UserService) Delete(accountId string) (*Response, error) { + return s.DeleteWithContext(context.Background(), accountId) +} + +// GetGroupsWithContext returns the groups which the user belongs to +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-groups-get +func (s *UserService) GetGroupsWithContext(ctx context.Context, accountId string) (*[]UserGroup, *Response, error) { + apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?accountId=%s", accountId) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + userGroups := new([]UserGroup) + resp, err := s.client.Do(req, userGroups) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return userGroups, resp, nil +} + +// GetGroups wraps GetGroupsWithContext using the background context. +func (s *UserService) GetGroups(accountId string) (*[]UserGroup, *Response, error) { + return s.GetGroupsWithContext(context.Background(), accountId) +} + +// GetSelfWithContext information about the current logged-in user +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-myself-get +func (s *UserService) GetSelfWithContext(ctx context.Context) (*User, *Response, error) { + const apiEndpoint = "rest/api/2/myself" + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + var user User + resp, err := s.client.Do(req, &user) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return &user, resp, nil +} + +// GetSelf wraps GetSelfWithContext using the background context. +func (s *UserService) GetSelf() (*User, *Response, error) { + return s.GetSelfWithContext(context.Background()) +} + +// WithMaxResults sets the max results to return +func WithMaxResults(maxResults int) userSearchF { + return func(s userSearch) userSearch { + s = append(s, userSearchParam{name: "maxResults", value: fmt.Sprintf("%d", maxResults)}) + return s + } +} + +// WithStartAt set the start pager +func WithStartAt(startAt int) userSearchF { + return func(s userSearch) userSearch { + s = append(s, userSearchParam{name: "startAt", value: fmt.Sprintf("%d", startAt)}) + return s + } +} + +// WithActive sets the active users lookup +func WithActive(active bool) userSearchF { + return func(s userSearch) userSearch { + s = append(s, userSearchParam{name: "includeActive", value: fmt.Sprintf("%t", active)}) + return s + } +} + +// WithInactive sets the inactive users lookup +func WithInactive(inactive bool) userSearchF { + return func(s userSearch) userSearch { + s = append(s, userSearchParam{name: "includeInactive", value: fmt.Sprintf("%t", inactive)}) + return s + } +} + +// WithUsername sets the username to search +func WithUsername(username string) userSearchF { + return func(s userSearch) userSearch { + s = append(s, userSearchParam{name: "username", value: username}) + return s + } +} + +// WithAccountId sets the account id to search +func WithAccountId(accountId string) userSearchF { + return func(s userSearch) userSearch { + s = append(s, userSearchParam{name: "accountId", value: accountId}) + return s + } +} + +// WithProperty sets the property (Property keys are specified by path) to search +func WithProperty(property string) userSearchF { + return func(s userSearch) userSearch { + s = append(s, userSearchParam{name: "property", value: property}) + return s + } +} + +// FindWithContext searches for user info from Jira: +// It can find users by email or display name using the query parameter +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-search-get +func (s *UserService) FindWithContext(ctx context.Context, property string, tweaks ...userSearchF) ([]User, *Response, error) { + search := []userSearchParam{ + { + name: "query", + value: property, + }, + } + for _, f := range tweaks { + search = f(search) + } + + var queryString = "" + for _, param := range search { + queryString += param.name + "=" + param.value + "&" + } + + apiEndpoint := fmt.Sprintf("/rest/api/2/user/search?%s", queryString[:len(queryString)-1]) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + users := []User{} + resp, err := s.client.Do(req, &users) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return users, resp, nil +} + +// Find wraps FindWithContext using the background context. +func (s *UserService) Find(property string, tweaks ...userSearchF) ([]User, *Response, error) { + return s.FindWithContext(context.Background(), property, tweaks...) +} diff --git a/onpremise/user_test.go b/onpremise/user_test.go new file mode 100644 index 0000000..f76d22c --- /dev/null +++ b/onpremise/user_test.go @@ -0,0 +1,192 @@ +package onpremise + +import ( + "fmt" + "net/http" + "testing" +) + +func TestUserService_Get_Success(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") + + fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","key":"fred", + "name":"fred","emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", + "24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", + "32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":true,"timeZone":"Australia/Sydney","groups":{"size":3,"items":[ + {"name":"jira-user","self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-user"},{"name":"jira-admin", + "self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-admin"},{"name":"important","self":"http://www.example.com/jira/rest/api/2/group?groupname=important" + }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) + }) + + if user, _, err := testClient.User.Get("000000000000000000000000"); err != nil { + t.Errorf("Error given: %s", err) + } else if user == nil { + t.Error("Expected user. User is nil") + } +} + +func TestUserService_GetByAccountID_Success(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") + + fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","accountId": "000000000000000000000000", + "name":"fred","emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", + "24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", + "32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":true,"timeZone":"Australia/Sydney","groups":{"size":3,"items":[ + {"name":"jira-user","self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-user"},{"name":"jira-admin", + "self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-admin"},{"name":"important","self":"http://www.example.com/jira/rest/api/2/group?groupname=important" + }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) + }) + + if user, _, err := testClient.User.GetByAccountID("000000000000000000000000"); err != nil { + t.Errorf("Error given: %s", err) + } else if user == nil { + t.Error("Expected user. User is nil") + } +} + +func TestUserService_Create(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/user") + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"name":"charlie","password":"abracadabra","emailAddress":"charlie@atlassian.com", + "displayName":"Charlie of Atlassian","applicationKeys":["jira-core"]}`) + }) + + u := &User{ + Name: "charlie", + Password: "abracadabra", + EmailAddress: "charlie@atlassian.com", + DisplayName: "Charlie of Atlassian", + ApplicationKeys: []string{"jira-core"}, + } + + if user, _, err := testClient.User.Create(u); err != nil { + t.Errorf("Error given: %s", err) + } else if user == nil { + t.Error("Expected user. User is nil") + } +} + +func TestUserService_Delete(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") + + w.WriteHeader(http.StatusNoContent) + }) + + resp, err := testClient.User.Delete("000000000000000000000000") + if err != nil { + t.Errorf("Error given: %s", err) + } + + if resp.StatusCode != http.StatusNoContent { + t.Errorf("Wrong status code: %d. Expected %d", resp.StatusCode, http.StatusNoContent) + } +} + +func TestUserService_GetGroups(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/user/groups", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/user/groups?accountId=000000000000000000000000") + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `[{"name":"jira-software-users","self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000"}]`) + }) + + if groups, _, err := testClient.User.GetGroups("000000000000000000000000"); err != nil { + t.Errorf("Error given: %s", err) + } else if groups == nil { + t.Error("Expected user groups. []UserGroup is nil") + } +} + +func TestUserService_GetSelf(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/myself", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/myself") + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","key":"fred", + "name":"fred","emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", + "24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", + "32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":true,"timeZone":"Australia/Sydney","groups":{"size":3,"items":[ + {"name":"jira-user","self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-user"},{"name":"jira-admin", + "self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-admin"},{"name":"important","self":"http://www.example.com/jira/rest/api/2/group?groupname=important" + }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) + }) + + if user, _, err := testClient.User.GetSelf(); err != nil { + t.Errorf("Error given: %s", err) + } else if user == nil { + t.Error("Expected user groups. []UserGroup is nil") + } else if user.Name != "fred" || + !user.Active || + user.DisplayName != "Fred F. User" { + t.Errorf("User JSON deserialized incorrectly") + } +} + +func TestUserService_Find_Success(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/user/search", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/user/search?query=fred@example.com") + + fmt.Fprint(w, `[{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","key":"fred", + "name":"fred","emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", + "24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", + "32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":true,"timeZone":"Australia/Sydney","groups":{"size":3,"items":[ + {"name":"jira-user","self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-user"},{"name":"jira-admin", + "self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-admin"},{"name":"important","self":"http://www.example.com/jira/rest/api/2/group?groupname=important" + }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}]`) + }) + + if user, _, err := testClient.User.Find("fred@example.com"); err != nil { + t.Errorf("Error given: %s", err) + } else if user == nil { + t.Error("Expected user. User is nil") + } +} + +func TestUserService_Find_SuccessParams(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/user/search", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/user/search?query=fred@example.com&startAt=100&maxResults=1000") + + fmt.Fprint(w, `[{"self":"http://www.example.com/jira/rest/api/2/user?query=fred","key":"fred", + "name":"fred","emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", + "24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", + "32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":true,"timeZone":"Australia/Sydney","groups":{"size":3,"items":[ + {"name":"jira-user","self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-user"},{"name":"jira-admin", + "self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-admin"},{"name":"important","self":"http://www.example.com/jira/rest/api/2/group?groupname=important" + }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}]`) + }) + + if user, _, err := testClient.User.Find("fred@example.com", WithStartAt(100), WithMaxResults(1000)); err != nil { + t.Errorf("Error given: %s", err) + } else if user == nil { + t.Error("Expected user. User is nil") + } +} diff --git a/onpremise/version.go b/onpremise/version.go new file mode 100644 index 0000000..f4a8266 --- /dev/null +++ b/onpremise/version.go @@ -0,0 +1,115 @@ +package onpremise + +import ( + "context" + "encoding/json" + "fmt" + "io" +) + +// VersionService handles Versions for the Jira instance / API. +// +// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/version +type VersionService struct { + client *Client +} + +// Version represents a single release version of a project +type Version struct { + Self string `json:"self,omitempty" structs:"self,omitempty"` + ID string `json:"id,omitempty" structs:"id,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + Description string `json:"description,omitempty" structs:"description,omitempty"` + Archived *bool `json:"archived,omitempty" structs:"archived,omitempty"` + Released *bool `json:"released,omitempty" structs:"released,omitempty"` + ReleaseDate string `json:"releaseDate,omitempty" structs:"releaseDate,omitempty"` + UserReleaseDate string `json:"userReleaseDate,omitempty" structs:"userReleaseDate,omitempty"` + ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"` // Unlike other IDs, this is returned as a number + StartDate string `json:"startDate,omitempty" structs:"startDate,omitempty"` +} + +// GetWithContext gets version info from Jira +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-get +func (s *VersionService) GetWithContext(ctx context.Context, versionID int) (*Version, *Response, error) { + apiEndpoint := fmt.Sprintf("/rest/api/2/version/%v", versionID) + req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + version := new(Version) + resp, err := s.client.Do(req, version) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return version, resp, nil +} + +// Get wraps GetWithContext using the background context. +func (s *VersionService) Get(versionID int) (*Version, *Response, error) { + return s.GetWithContext(context.Background(), versionID) +} + +// CreateWithContext creates a version in Jira. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-post +func (s *VersionService) CreateWithContext(ctx context.Context, version *Version) (*Version, *Response, error) { + apiEndpoint := "/rest/api/2/version" + req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, version) + if err != nil { + return nil, nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return nil, resp, err + } + + responseVersion := new(Version) + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + e := fmt.Errorf("could not read the returned data") + return nil, resp, NewJiraError(resp, e) + } + err = json.Unmarshal(data, responseVersion) + if err != nil { + e := fmt.Errorf("could not unmarshall the data into struct") + return nil, resp, NewJiraError(resp, e) + } + return responseVersion, resp, nil +} + +// Create wraps CreateWithContext using the background context. +func (s *VersionService) Create(version *Version) (*Version, *Response, error) { + return s.CreateWithContext(context.Background(), version) +} + +// UpdateWithContext updates a version from a JSON representation. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-put +// Caller must close resp.Body +func (s *VersionService) UpdateWithContext(ctx context.Context, version *Version) (*Version, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/version/%v", version.ID) + req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, version) + if err != nil { + return nil, nil, err + } + resp, err := s.client.Do(req, nil) + if err != nil { + jerr := NewJiraError(resp, err) + return nil, resp, jerr + } + + // This is just to follow the rest of the API's convention of returning a version. + // Returning the same pointer here is pointless, so we return a copy instead. + ret := *version + return &ret, resp, nil +} + +// Update wraps UpdateWithContext using the background context. +// Caller must close resp.Body +func (s *VersionService) Update(version *Version) (*Version, *Response, error) { + return s.UpdateWithContext(context.Background(), version) +} diff --git a/onpremise/version_test.go b/onpremise/version_test.go new file mode 100644 index 0000000..7ec3ca4 --- /dev/null +++ b/onpremise/version_test.go @@ -0,0 +1,112 @@ +package onpremise + +import ( + "fmt" + "net/http" + "testing" +) + +func TestVersionService_Get_Success(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/version/10002", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/version/10002") + + fmt.Fprint(w, `{ + "self": "http://www.example.com/jira/rest/api/2/version/10002", + "id": "10002", + "description": "An excellent version", + "name": "New Version 1", + "archived": false, + "released": true, + "releaseDate": "2010-07-06", + "overdue": true, + "userReleaseDate": "6/Jul/2010", + "startDate" : "2010-07-01", + "projectId": 10000 + }`) + }) + + version, _, err := testClient.Version.Get(10002) + if version == nil { + t.Error("Expected version. Issue is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestVersionService_Create(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/version", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/api/2/version") + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{ + "description": "An excellent version", + "name": "New Version 1", + "archived": false, + "released": true, + "releaseDate": "2010-07-06", + "userReleaseDate": "6/Jul/2010", + "project": "PXA", + "projectId": 10000 + }`) + }) + + v := &Version{ + Name: "New Version 1", + Description: "An excellent version", + ProjectID: 10000, + Released: Bool(true), + Archived: Bool(false), + ReleaseDate: "2010-07-06", + UserReleaseDate: "6/Jul/2010", + StartDate: "2018-07-01", + } + + version, _, err := testClient.Version.Create(v) + if version == nil { + t.Error("Expected version. Version is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestServiceService_Update(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/version/10002", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testRequestURL(t, r, "/rest/api/2/version/10002") + fmt.Fprint(w, `{ + "description": "An excellent updated version", + "name": "New Updated Version 1", + "archived": false, + "released": true, + "releaseDate": "2010-07-06", + "userReleaseDate": "6/Jul/2010", + "startDate" : "2010-07-01", + "project": "PXA", + "projectId": 10000 + }`) + }) + + v := &Version{ + ID: "10002", + Name: "New Updated Version 1", + Description: "An excellent updated version", + } + + version, _, err := testClient.Version.Update(v) + if version == nil { + t.Error("Expected version. Version is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} diff --git a/request_context.go b/request_context.go deleted file mode 100644 index 9f3e264..0000000 --- a/request_context.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// This file provides glue to use Context in `http.Request` with -// Go version 1.13 and higher. - -// The function `http.NewRequestWithContext` has been added in Go 1.13. -// Before the release 1.13, to use Context we need creat `http.Request` -// then use the method `WithContext` to create a new `http.Request` -// with Context from the existing `http.Request`. -// -// Doc: https://golang.org/doc/go1.13#net/http - -package jira - -import ( - "context" - "io" - "net/http" -) - -func newRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*http.Request, error) { - return http.NewRequestWithContext(ctx, method, url, body) -} diff --git a/request_legacy.go b/request_legacy.go deleted file mode 100644 index 93eb65e..0000000 --- a/request_legacy.go +++ /dev/null @@ -1,29 +0,0 @@ -//go:build !go1.13 -// +build !go1.13 - -// This file provides glue to use Context in `http.Request` with -// Go version before 1.13. - -// The function `http.NewRequestWithContext` has been added in Go 1.13. -// Before the release 1.13, to use Context we need creat `http.Request` -// then use the method `WithContext` to create a new `http.Request` -// with Context from the existing `http.Request`. -// -// Doc: https://golang.org/doc/go1.13#net/http - -package jira - -import ( - "context" - "io" - "net/http" -) - -func newRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*http.Request, error) { - r, err := http.NewRequest(method, url, body) - if err != nil { - return nil, err - } - - return r.WithContext(ctx), nil -} diff --git a/mocks/all_boards.json b/testing/mock-data/all_boards.json similarity index 100% rename from mocks/all_boards.json rename to testing/mock-data/all_boards.json diff --git a/mocks/all_boards_filtered.json b/testing/mock-data/all_boards_filtered.json similarity index 100% rename from mocks/all_boards_filtered.json rename to testing/mock-data/all_boards_filtered.json diff --git a/mocks/all_fields.json b/testing/mock-data/all_fields.json similarity index 100% rename from mocks/all_fields.json rename to testing/mock-data/all_fields.json diff --git a/mocks/all_filters.json b/testing/mock-data/all_filters.json similarity index 100% rename from mocks/all_filters.json rename to testing/mock-data/all_filters.json diff --git a/mocks/all_issuelinktypes.json b/testing/mock-data/all_issuelinktypes.json similarity index 100% rename from mocks/all_issuelinktypes.json rename to testing/mock-data/all_issuelinktypes.json diff --git a/mocks/all_permissionschemes.json b/testing/mock-data/all_permissionschemes.json similarity index 100% rename from mocks/all_permissionschemes.json rename to testing/mock-data/all_permissionschemes.json diff --git a/mocks/all_priorities.json b/testing/mock-data/all_priorities.json similarity index 100% rename from mocks/all_priorities.json rename to testing/mock-data/all_priorities.json diff --git a/mocks/all_projects.json b/testing/mock-data/all_projects.json similarity index 100% rename from mocks/all_projects.json rename to testing/mock-data/all_projects.json diff --git a/mocks/all_resolutions.json b/testing/mock-data/all_resolutions.json similarity index 100% rename from mocks/all_resolutions.json rename to testing/mock-data/all_resolutions.json diff --git a/mocks/all_roles.json b/testing/mock-data/all_roles.json similarity index 100% rename from mocks/all_roles.json rename to testing/mock-data/all_roles.json diff --git a/mocks/all_statuscategories.json b/testing/mock-data/all_statuscategories.json similarity index 100% rename from mocks/all_statuscategories.json rename to testing/mock-data/all_statuscategories.json diff --git a/mocks/all_statuses.json b/testing/mock-data/all_statuses.json similarity index 100% rename from mocks/all_statuses.json rename to testing/mock-data/all_statuses.json diff --git a/mocks/board_configuration.json b/testing/mock-data/board_configuration.json similarity index 100% rename from mocks/board_configuration.json rename to testing/mock-data/board_configuration.json diff --git a/mocks/favourite_filters.json b/testing/mock-data/favourite_filters.json similarity index 100% rename from mocks/favourite_filters.json rename to testing/mock-data/favourite_filters.json diff --git a/mocks/filter.json b/testing/mock-data/filter.json similarity index 100% rename from mocks/filter.json rename to testing/mock-data/filter.json diff --git a/mocks/issues_in_sprint.json b/testing/mock-data/issues_in_sprint.json similarity index 100% rename from mocks/issues_in_sprint.json rename to testing/mock-data/issues_in_sprint.json diff --git a/mocks/my_filters.json b/testing/mock-data/my_filters.json similarity index 100% rename from mocks/my_filters.json rename to testing/mock-data/my_filters.json diff --git a/mocks/no_permissionscheme.json b/testing/mock-data/no_permissionscheme.json similarity index 100% rename from mocks/no_permissionscheme.json rename to testing/mock-data/no_permissionscheme.json diff --git a/mocks/no_permissionschemes.json b/testing/mock-data/no_permissionschemes.json similarity index 100% rename from mocks/no_permissionschemes.json rename to testing/mock-data/no_permissionschemes.json diff --git a/mocks/no_role.json b/testing/mock-data/no_role.json similarity index 100% rename from mocks/no_role.json rename to testing/mock-data/no_role.json diff --git a/mocks/no_roles.json b/testing/mock-data/no_roles.json similarity index 100% rename from mocks/no_roles.json rename to testing/mock-data/no_roles.json diff --git a/mocks/permissionscheme.json b/testing/mock-data/permissionscheme.json similarity index 100% rename from mocks/permissionscheme.json rename to testing/mock-data/permissionscheme.json diff --git a/mocks/project.json b/testing/mock-data/project.json similarity index 100% rename from mocks/project.json rename to testing/mock-data/project.json diff --git a/mocks/remote_links.json b/testing/mock-data/remote_links.json similarity index 100% rename from mocks/remote_links.json rename to testing/mock-data/remote_links.json diff --git a/mocks/role.json b/testing/mock-data/role.json similarity index 100% rename from mocks/role.json rename to testing/mock-data/role.json diff --git a/mocks/search_filters.json b/testing/mock-data/search_filters.json similarity index 100% rename from mocks/search_filters.json rename to testing/mock-data/search_filters.json diff --git a/mocks/sprints.json b/testing/mock-data/sprints.json similarity index 100% rename from mocks/sprints.json rename to testing/mock-data/sprints.json diff --git a/mocks/sprints_filtered.json b/testing/mock-data/sprints_filtered.json similarity index 100% rename from mocks/sprints_filtered.json rename to testing/mock-data/sprints_filtered.json diff --git a/mocks/transitions.json b/testing/mock-data/transitions.json similarity index 100% rename from mocks/transitions.json rename to testing/mock-data/transitions.json From bda468a25ea4dd8e456c9b7bc725df470a9f355a Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 18:17:29 +0200 Subject: [PATCH 012/189] Changes to both (OnPremise and Cloud) clients to make it more future proof and configurable (#509) * Moved all Sub-Services to a common service struct * NewClient: Change order of arguments, baseUrl first, http client second * Client: Add default user agent to identify the client * addOptions: Rename opt to opts, because those can be multiple * Delete NewRawRequestWithContext, because NewRawRequest requires now a context Every time we make a request, we should deal with the context. We aim to avoid requests without any context. That is why we make this the new default * Add Client() method to get the HTTP client * Add Changelog entries and Migration entries * Delete NewRequestWithContext, because NewRequest requires now a context * Migrate Client changes from Cloud to OnPremise * Moved all Sub-Services to a common service struct * NewClient: Change order of arguments, baseUrl first, http client second * Client: Add default user agent to identify the client * addOptions: Rename opt to opts, because those can be multiple * Delete NewRawRequestWithContext, because NewRawRequest requires now a context * Add Client() method to get the HTTP client * Add Changelog entries and Migration entries * Delete NewRequestWithContext, because NewRequest requires now a context --- CHANGELOG.md | 85 ++++++++++ cloud/auth_transport_basic_test.go | 5 +- cloud/auth_transport_cookie.go | 1 + cloud/auth_transport_cookie_test.go | 13 +- cloud/auth_transport_jwt_test.go | 2 +- ...th_transport_personal_access_token_test.go | 2 +- cloud/authentication.go | 6 +- cloud/board.go | 16 +- cloud/component.go | 6 +- cloud/customer.go | 6 +- cloud/error_test.go | 9 +- cloud/examples/addlabel/main.go | 2 +- cloud/examples/basicauth/main.go | 2 +- cloud/examples/create/main.go | 2 +- cloud/examples/createwithcustomfields/main.go | 2 +- cloud/examples/do/main.go | 5 +- cloud/examples/ignorecerts/main.go | 2 +- cloud/examples/jql/main.go | 2 +- cloud/examples/newclient/main.go | 2 +- cloud/examples/pagination/main.go | 2 +- cloud/examples/renderedfields/main.go | 2 +- cloud/examples/searchpages/main.go | 2 +- cloud/field.go | 6 +- cloud/filter.go | 14 +- cloud/group.go | 12 +- cloud/issue.go | 56 +++--- cloud/issuelinktype.go | 14 +- cloud/jira.go | 159 ++++++++++-------- cloud/jira_test.go | 64 +++---- cloud/metaissue.go | 4 +- cloud/organization.go | 26 ++- cloud/permissionscheme.go | 9 +- cloud/priority.go | 6 +- cloud/project.go | 10 +- cloud/request.go | 8 +- cloud/resolution.go | 6 +- cloud/role.go | 8 +- cloud/servicedesk.go | 16 +- cloud/sprint.go | 10 +- cloud/status.go | 6 +- cloud/statuscategory.go | 6 +- cloud/user.go | 18 +- cloud/version.go | 10 +- onpremise/auth_transport_basic_test.go | 5 +- onpremise/auth_transport_cookie.go | 1 + onpremise/auth_transport_cookie_test.go | 13 +- onpremise/auth_transport_jwt_test.go | 2 +- ...th_transport_personal_access_token_test.go | 2 +- onpremise/authentication.go | 6 +- onpremise/board.go | 16 +- onpremise/component.go | 6 +- onpremise/customer.go | 6 +- onpremise/error_test.go | 9 +- onpremise/examples/addlabel/main.go | 4 +- onpremise/examples/basicauth/main.go | 4 +- onpremise/examples/create/main.go | 4 +- .../examples/createwithcustomfields/main.go | 4 +- onpremise/examples/do/main.go | 7 +- onpremise/examples/ignorecerts/main.go | 4 +- onpremise/examples/jql/main.go | 4 +- onpremise/examples/newclient/main.go | 4 +- onpremise/examples/pagination/main.go | 4 +- onpremise/examples/renderedfields/main.go | 4 +- onpremise/examples/searchpages/main.go | 4 +- onpremise/field.go | 6 +- onpremise/filter.go | 14 +- onpremise/group.go | 12 +- onpremise/issue.go | 56 +++--- onpremise/issuelinktype.go | 14 +- onpremise/jira.go | 159 ++++++++++-------- onpremise/jira_test.go | 64 +++---- onpremise/metaissue.go | 4 +- onpremise/organization.go | 26 ++- onpremise/permissionscheme.go | 9 +- onpremise/priority.go | 6 +- onpremise/project.go | 10 +- onpremise/request.go | 8 +- onpremise/resolution.go | 6 +- onpremise/role.go | 8 +- onpremise/servicedesk.go | 16 +- onpremise/sprint.go | 10 +- onpremise/status.go | 6 +- onpremise/statuscategory.go | 6 +- onpremise/user.go | 18 +- onpremise/version.go | 10 +- 85 files changed, 612 insertions(+), 613 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35dd84e..7f90595 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,91 @@ import ( ) ``` +#### Init a new client + +The order of arguments in the `jira.NewClient` has changed: + +1. The base URL of your JIRA instance +2. A HTTP client (optional) + +Before: + +```go +jira.NewClient(nil, "https://issues.apache.org/jira/") +``` + +After: + +```go +jira.NewClient("https://issues.apache.org/jira/", nil) +``` + +#### User Agent + +The client will identify itself via a UserAgent `go-jira/2.0.0`. + +#### `NewRawRequestWithContext` removed, `NewRawRequest` requires `context` + +The function `client.NewRawRequestWithContext()` has been removed. +`client.NewRawRequest()` accepts now a context as the first argument. +This is a drop in replacement. + +Before: + +```go +client.NewRawRequestWithContext(context.Background(), "GET", .....) +``` + +After: + +```go +client.NewRawRequest(context.Background(), "GET", .....) +``` + +For people who used `jira.NewRawRequest()`: You need to pass a context as the first argument. +Like + +```go +client.NewRawRequest(context.Background(), "GET", .....) +``` + +#### `NewRequestWithContext` removed, `NewRequest` requires `context` + +The function `client.NewRequestWithContext()` has been removed. +`client.NewRequest()` accepts now a context as the first argument. +This is a drop in replacement. + +Before: + +```go +client.NewRequestWithContext(context.Background(), "GET", .....) +``` + +After: + +```go +client.NewRequest(context.Background(), "GET", .....) +``` + +For people who used `jira.NewRequest()`: You need to pass a context as the first argument. +Like + +```go +client.NewRequest(context.Background(), "GET", .....) +``` + +### Breaking changes + +* Jira On-Premise and Jira Cloud have now different clients, because the API differs +* `client.NewRawRequestWithContext()` has been removed in favor of `client.NewRawRequest()`, which requires now a context as first argument +* `client.NewRequestWithContext()` has been removed in favor of `client.NewRequest()`, which requires now a context as first argument + +### Features + +* UserAgent: Client HTTP calls are now identifable via a User Agent. This user agent can be configured (default: `go-jira/2.0.0`) +* The underlying used HTTP client for API calls can be retrieved via `client.Client()` + + ### Changes ## [1.13.0](https://github.com/andygrunwald/go-jira/compare/v1.11.1...v1.13.0) (2020-10-25) diff --git a/cloud/auth_transport_basic_test.go b/cloud/auth_transport_basic_test.go index db4704e..c7db151 100644 --- a/cloud/auth_transport_basic_test.go +++ b/cloud/auth_transport_basic_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "net/http" "testing" ) @@ -29,8 +30,8 @@ func TestBasicAuthTransport(t *testing.T) { Password: password, } - basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) - req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) + req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) basicAuthClient.Do(req, nil) } diff --git a/cloud/auth_transport_cookie.go b/cloud/auth_transport_cookie.go index bd6e4b4..ddde946 100644 --- a/cloud/auth_transport_cookie.go +++ b/cloud/auth_transport_cookie.go @@ -89,6 +89,7 @@ func (t *CookieAuthTransport) buildAuthRequest() (*http.Request, error) { b := new(bytes.Buffer) json.NewEncoder(b).Encode(body) + // TODO Use a context here req, err := http.NewRequest("POST", t.AuthURL, b) if err != nil { return nil, err diff --git a/cloud/auth_transport_cookie_test.go b/cloud/auth_transport_cookie_test.go index c9d1cfc..14dde2c 100644 --- a/cloud/auth_transport_cookie_test.go +++ b/cloud/auth_transport_cookie_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "net/http" "net/http/httptest" "testing" @@ -36,8 +37,8 @@ func TestCookieAuthTransport_SessionObject_Exists(t *testing.T) { SessionObject: []*http.Cookie{testCookie}, } - basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) - req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) + req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) basicAuthClient.Do(req, nil) } @@ -72,8 +73,8 @@ func TestCookieAuthTransport_SessionObject_ExistsWithEmptyCookie(t *testing.T) { SessionObject: []*http.Cookie{emptyCookie, testCookie}, } - basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) - req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) + req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) basicAuthClient.Do(req, nil) } @@ -113,7 +114,7 @@ func TestCookieAuthTransport_SessionObject_DoesNotExist(t *testing.T) { AuthURL: ts.URL, } - basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) - req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) + req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) basicAuthClient.Do(req, nil) } diff --git a/cloud/auth_transport_jwt_test.go b/cloud/auth_transport_jwt_test.go index e577bb6..2ce50f2 100644 --- a/cloud/auth_transport_jwt_test.go +++ b/cloud/auth_transport_jwt_test.go @@ -26,6 +26,6 @@ func TestJWTAuthTransport_HeaderContainsJWT(t *testing.T) { } }) - jwtClient, _ := NewClient(jwtTransport.Client(), testServer.URL) + jwtClient, _ := NewClient(testServer.URL, jwtTransport.Client()) jwtClient.Issue.Get("TEST-1", nil) } diff --git a/cloud/auth_transport_personal_access_token_test.go b/cloud/auth_transport_personal_access_token_test.go index 5ad4926..397a8e8 100644 --- a/cloud/auth_transport_personal_access_token_test.go +++ b/cloud/auth_transport_personal_access_token_test.go @@ -23,7 +23,7 @@ func TestPATAuthTransport_HeaderContainsAuth(t *testing.T) { } }) - client, _ := NewClient(patTransport.Client(), testServer.URL) + client, _ := NewClient(testServer.URL, patTransport.Client()) client.User.GetSelf() } diff --git a/cloud/authentication.go b/cloud/authentication.go index fc0828c..caa7c31 100644 --- a/cloud/authentication.go +++ b/cloud/authentication.go @@ -67,7 +67,7 @@ func (s *AuthenticationService) AcquireSessionCookieWithContext(ctx context.Cont password, } - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, body) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, body) if err != nil { return false, err } @@ -133,7 +133,7 @@ func (s *AuthenticationService) LogoutWithContext(ctx context.Context) error { } apiEndpoint := "rest/auth/1/session" - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return fmt.Errorf("creating the request to log the user out failed : %s", err) } @@ -174,7 +174,7 @@ func (s *AuthenticationService) GetCurrentUserWithContext(ctx context.Context) ( } apiEndpoint := "rest/auth/1/session" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, fmt.Errorf("could not create request for getting user info : %s", err) } diff --git a/cloud/board.go b/cloud/board.go index 25b4783..7ef0f15 100644 --- a/cloud/board.go +++ b/cloud/board.go @@ -10,9 +10,7 @@ import ( // BoardService handles Agile Boards for the Jira instance / API. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/server/ -type BoardService struct { - client *Client -} +type BoardService service // BoardsList reflects a list of agile boards type BoardsList struct { @@ -136,7 +134,7 @@ func (s *BoardService) GetAllBoardsWithContext(ctx context.Context, opt *BoardLi if err != nil { return nil, nil, err } - req, err := s.client.NewRequestWithContext(ctx, "GET", url, nil) + req, err := s.client.NewRequest(ctx, "GET", url, nil) if err != nil { return nil, nil, err } @@ -162,7 +160,7 @@ func (s *BoardService) GetAllBoards(opt *BoardListOptions) (*BoardsList, *Respon // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard func (s *BoardService) GetBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -192,7 +190,7 @@ func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) { // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard func (s *BoardService) CreateBoardWithContext(ctx context.Context, board *Board) (*Board, *Response, error) { apiEndpoint := "rest/agile/1.0/board" - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, board) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, board) if err != nil { return nil, nil, err } @@ -218,7 +216,7 @@ func (s *BoardService) CreateBoard(board *Board) (*Board, *Response, error) { // Caller must close resp.Body func (s *BoardService) DeleteBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -269,7 +267,7 @@ func (s *BoardService) GetAllSprintsWithOptionsWithContext(ctx context.Context, if err != nil { return nil, nil, err } - req, err := s.client.NewRequestWithContext(ctx, "GET", url, nil) + req, err := s.client.NewRequest(ctx, "GET", url, nil) if err != nil { return nil, nil, err } @@ -293,7 +291,7 @@ func (s *BoardService) GetAllSprintsWithOptions(boardID int, options *GetAllSpri func (s *BoardService) GetBoardConfigurationWithContext(ctx context.Context, boardID int) (*BoardConfiguration, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/configuration", boardID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err diff --git a/cloud/component.go b/cloud/component.go index 51a36b6..ad18e43 100644 --- a/cloud/component.go +++ b/cloud/component.go @@ -4,9 +4,7 @@ import "context" // ComponentService handles components for the Jira instance / API.// // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.1/#api/2/component -type ComponentService struct { - client *Client -} +type ComponentService service // CreateComponentOptions are passed to the ComponentService.Create function to create a new Jira component type CreateComponentOptions struct { @@ -23,7 +21,7 @@ type CreateComponentOptions struct { // CreateWithContext creates a new Jira component based on the given options. func (s *ComponentService) CreateWithContext(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) { apiEndpoint := "rest/api/2/component" - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, options) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, options) if err != nil { return nil, nil, err } diff --git a/cloud/customer.go b/cloud/customer.go index fb43ff9..095c81e 100644 --- a/cloud/customer.go +++ b/cloud/customer.go @@ -6,9 +6,7 @@ import ( ) // CustomerService handles ServiceDesk customers for the Jira instance / API. -type CustomerService struct { - client *Client -} +type CustomerService service // Customer represents a ServiceDesk customer. type Customer struct { @@ -52,7 +50,7 @@ func (c *CustomerService) CreateWithContext(ctx context.Context, email, displayN DisplayName: displayName, } - req, err := c.client.NewRequestWithContext(ctx, http.MethodPost, apiEndpoint, payload) + req, err := c.client.NewRequest(ctx, http.MethodPost, apiEndpoint, payload) if err != nil { return nil, nil, err } diff --git a/cloud/error_test.go b/cloud/error_test.go index cb60c1c..b8e55cd 100644 --- a/cloud/error_test.go +++ b/cloud/error_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "errors" "fmt" "net/http" @@ -17,7 +18,7 @@ func TestError_NewJiraError(t *testing.T) { fmt.Fprint(w, `{"errorMessages":["Issue does not exist or you do not have permission to see it."],"errors":{}}`) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, errors.New("Original http error")) @@ -51,7 +52,7 @@ func TestError_NoJSON(t *testing.T) { fmt.Fprint(w, `Original message body`) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, errors.New("Original http error")) @@ -71,7 +72,7 @@ func TestError_Unauthorized_NilError(t *testing.T) { fmt.Fprint(w, `User is not authorized`) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, nil) @@ -90,7 +91,7 @@ func TestError_BadJSON(t *testing.T) { fmt.Fprint(w, `Not JSON`) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, errors.New("Original http error")) diff --git a/cloud/examples/addlabel/main.go b/cloud/examples/addlabel/main.go index d7dc3fa..ca5a6f7 100644 --- a/cloud/examples/addlabel/main.go +++ b/cloud/examples/addlabel/main.go @@ -38,7 +38,7 @@ func main() { Password: strings.TrimSpace(password), } - client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) if err != nil { fmt.Printf("\nerror: %v\n", err) return diff --git a/cloud/examples/basicauth/main.go b/cloud/examples/basicauth/main.go index c9d6eb0..7221d87 100644 --- a/cloud/examples/basicauth/main.go +++ b/cloud/examples/basicauth/main.go @@ -30,7 +30,7 @@ func main() { Password: strings.TrimSpace(password), } - client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) if err != nil { fmt.Printf("\nerror: %v\n", err) return diff --git a/cloud/examples/create/main.go b/cloud/examples/create/main.go index b803de0..2dc3837 100644 --- a/cloud/examples/create/main.go +++ b/cloud/examples/create/main.go @@ -29,7 +29,7 @@ func main() { Password: strings.TrimSpace(password), } - client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) if err != nil { fmt.Printf("\nerror: %v\n", err) return diff --git a/cloud/examples/createwithcustomfields/main.go b/cloud/examples/createwithcustomfields/main.go index 8ebe424..68b32b5 100644 --- a/cloud/examples/createwithcustomfields/main.go +++ b/cloud/examples/createwithcustomfields/main.go @@ -36,7 +36,7 @@ func main() { Password: strings.TrimSpace(password), } - client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) if err != nil { fmt.Printf("\nerror: %v\n", err) os.Exit(1) diff --git a/cloud/examples/do/main.go b/cloud/examples/do/main.go index b13adc2..eee2747 100644 --- a/cloud/examples/do/main.go +++ b/cloud/examples/do/main.go @@ -1,14 +1,15 @@ package main import ( + "context" "fmt" jira "github.com/andygrunwald/go-jira/cloud" ) func main() { - jiraClient, _ := jira.NewClient(nil, "https://jira.atlassian.com/") - req, _ := jiraClient.NewRequest("GET", "/rest/api/2/project", nil) + jiraClient, _ := jira.NewClient("https://jira.atlassian.com/", nil) + req, _ := jiraClient.NewRequest(context.Background(), "GET", "/rest/api/2/project", nil) projects := new([]jira.Project) res, err := jiraClient.Do(req, projects) diff --git a/cloud/examples/ignorecerts/main.go b/cloud/examples/ignorecerts/main.go index e5325bc..525cd51 100644 --- a/cloud/examples/ignorecerts/main.go +++ b/cloud/examples/ignorecerts/main.go @@ -14,7 +14,7 @@ func main() { } client := &http.Client{Transport: tr} - jiraClient, _ := jira.NewClient(client, "https://issues.apache.org/jira/") + jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", client) issue, _, _ := jiraClient.Issue.Get("MESOS-3325", nil) fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary) diff --git a/cloud/examples/jql/main.go b/cloud/examples/jql/main.go index de980db..ce3ba45 100644 --- a/cloud/examples/jql/main.go +++ b/cloud/examples/jql/main.go @@ -7,7 +7,7 @@ import ( ) func main() { - jiraClient, _ := jira.NewClient(nil, "https://issues.apache.org/jira/") + jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", nil) // Running JQL query diff --git a/cloud/examples/newclient/main.go b/cloud/examples/newclient/main.go index 9b259d8..c03461d 100644 --- a/cloud/examples/newclient/main.go +++ b/cloud/examples/newclient/main.go @@ -7,7 +7,7 @@ import ( ) func main() { - jiraClient, _ := jira.NewClient(nil, "https://issues.apache.org/jira/") + jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", nil) issue, _, _ := jiraClient.Issue.Get("MESOS-3325", nil) fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary) diff --git a/cloud/examples/pagination/main.go b/cloud/examples/pagination/main.go index 69a9857..07c52ee 100644 --- a/cloud/examples/pagination/main.go +++ b/cloud/examples/pagination/main.go @@ -38,7 +38,7 @@ func GetAllIssues(client *jira.Client, searchString string) ([]jira.Issue, error } func main() { - jiraClient, err := jira.NewClient(nil, "https://issues.apache.org/jira/") + jiraClient, err := jira.NewClient("https://issues.apache.org/jira/", nil) if err != nil { panic(err) } diff --git a/cloud/examples/renderedfields/main.go b/cloud/examples/renderedfields/main.go index 92e8aa9..274498f 100644 --- a/cloud/examples/renderedfields/main.go +++ b/cloud/examples/renderedfields/main.go @@ -43,7 +43,7 @@ func main() { tp = ba.Client() } - client, err := jira.NewClient(tp, strings.TrimSpace(jiraURL)) + client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp) if err != nil { fmt.Printf("\nerror: %v\n", err) return diff --git a/cloud/examples/searchpages/main.go b/cloud/examples/searchpages/main.go index f1dac69..51db937 100644 --- a/cloud/examples/searchpages/main.go +++ b/cloud/examples/searchpages/main.go @@ -34,7 +34,7 @@ func main() { Password: strings.TrimSpace(password), } - client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) if err != nil { log.Fatal(err) } diff --git a/cloud/field.go b/cloud/field.go index 4cc369a..c53a14f 100644 --- a/cloud/field.go +++ b/cloud/field.go @@ -5,9 +5,7 @@ import "context" // FieldService handles fields for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Field -type FieldService struct { - client *Client -} +type FieldService service // Field represents a field of a Jira issue. type Field struct { @@ -36,7 +34,7 @@ type FieldSchema struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get func (s *FieldService) GetListWithContext(ctx context.Context) ([]Field, *Response, error) { apiEndpoint := "rest/api/2/field" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/filter.go b/cloud/filter.go index 6a0937c..a92e0e5 100644 --- a/cloud/filter.go +++ b/cloud/filter.go @@ -10,9 +10,7 @@ import ( // FilterService handles fields for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Filter -type FilterService struct { - client *Client -} +type FilterService service // Filter represents a Filter in Jira type Filter struct { @@ -125,7 +123,7 @@ func (fs *FilterService) GetListWithContext(ctx context.Context) ([]*Filter, *Re options := &GetQueryOptions{} apiEndpoint := "rest/api/2/filter" - req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -153,7 +151,7 @@ func (fs *FilterService) GetList() ([]*Filter, *Response, error) { // GetFavouriteListWithContext retrieves the user's favourited filters from Jira func (fs *FilterService) GetFavouriteListWithContext(ctx context.Context) ([]*Filter, *Response, error) { apiEndpoint := "rest/api/2/filter/favourite" - req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -174,7 +172,7 @@ func (fs *FilterService) GetFavouriteList() ([]*Filter, *Response, error) { // GetWithContext retrieves a single Filter from Jira func (fs *FilterService) GetWithContext(ctx context.Context, filterID int) (*Filter, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/filter/%d", filterID) - req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -202,7 +200,7 @@ func (fs *FilterService) GetMyFiltersWithContext(ctx context.Context, opts *GetM if err != nil { return nil, nil, err } - req, err := fs.client.NewRequestWithContext(ctx, "GET", url, nil) + req, err := fs.client.NewRequest(ctx, "GET", url, nil) if err != nil { return nil, nil, err } @@ -230,7 +228,7 @@ func (fs *FilterService) SearchWithContext(ctx context.Context, opt *FilterSearc if err != nil { return nil, nil, err } - req, err := fs.client.NewRequestWithContext(ctx, "GET", url, nil) + req, err := fs.client.NewRequest(ctx, "GET", url, nil) if err != nil { return nil, nil, err } diff --git a/cloud/group.go b/cloud/group.go index 39fb295..29a70d1 100644 --- a/cloud/group.go +++ b/cloud/group.go @@ -9,9 +9,7 @@ import ( // GroupService handles Groups for the Jira instance / API. // // Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group -type GroupService struct { - client *Client -} +type GroupService service // groupMembersResult is only a small wrapper around the Group* methods // to be able to parse the results @@ -68,7 +66,7 @@ type GroupSearchOptions struct { // WARNING: This API only returns the first page of group members func (s *GroupService) GetWithContext(ctx context.Context, name string) ([]GroupMember, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -105,7 +103,7 @@ func (s *GroupService) GetWithOptionsWithContext(ctx context.Context, name strin options.IncludeInactiveUsers, ) } - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -132,7 +130,7 @@ func (s *GroupService) AddWithContext(ctx context.Context, groupname string, use Name string `json:"name"` } user.Name = username - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, &user) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, &user) if err != nil { return nil, nil, err } @@ -158,7 +156,7 @@ func (s *GroupService) Add(groupname string, username string) (*Group, *Response // Caller must close resp.Body func (s *GroupService) RemoveWithContext(ctx context.Context, groupname string, username string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s&username=%s", groupname, username) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return nil, err } diff --git a/cloud/issue.go b/cloud/issue.go index dc17183..e18d0db 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -27,9 +27,7 @@ const ( // IssueService handles Issues for the Jira instance / API. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue -type IssueService struct { - client *Client -} +type IssueService service // UpdateQueryOptions specifies the optional parameters to the Edit issue type UpdateQueryOptions struct { @@ -617,7 +615,7 @@ type RemoteLinkStatus struct { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getIssue func (s *IssueService) GetWithContext(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -651,7 +649,7 @@ func (s *IssueService) Get(issueID string, options *GetQueryOptions) (*Issue, *R // Caller must close resp.Body. func (s *IssueService) DownloadAttachmentWithContext(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("secure/attachment/%s/", attachmentID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, err } @@ -719,7 +717,7 @@ func (s *IssueService) PostAttachment(issueID string, r io.Reader, attachmentNam func (s *IssueService) DeleteAttachmentWithContext(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/attachment/%s", attachmentID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return nil, err } @@ -744,7 +742,7 @@ func (s *IssueService) DeleteAttachment(attachmentID string) (*Response, error) func (s *IssueService) DeleteLinkWithContext(ctx context.Context, linkID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLink/%s", linkID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return nil, err } @@ -771,7 +769,7 @@ func (s *IssueService) DeleteLink(linkID string) (*Response, error) { func (s *IssueService) GetWorklogsWithContext(ctx context.Context, issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -816,7 +814,7 @@ func WithQueryOptions(options interface{}) func(*http.Request) error { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-createIssues func (s *IssueService) CreateWithContext(ctx context.Context, issue *Issue) (*Issue, *Response, error) { apiEndpoint := "rest/api/2/issue" - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, issue) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, issue) if err != nil { return nil, nil, err } @@ -855,7 +853,7 @@ func (s *IssueService) UpdateWithOptionsWithContext(ctx context.Context, issue * if err != nil { return nil, nil, err } - req, err := s.client.NewRequestWithContext(ctx, "PUT", url, issue) + req, err := s.client.NewRequest(ctx, "PUT", url, issue) if err != nil { return nil, nil, err } @@ -895,7 +893,7 @@ func (s *IssueService) Update(issue *Issue) (*Issue, *Response, error) { // Caller must close resp.Body func (s *IssueService) UpdateIssueWithContext(ctx context.Context, jiraID string, data map[string]interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", jiraID) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, data) + req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, data) if err != nil { return nil, err } @@ -920,7 +918,7 @@ func (s *IssueService) UpdateIssue(jiraID string, data map[string]interface{}) ( // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-addComment func (s *IssueService) AddCommentWithContext(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment", issueID) - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, comment) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, comment) if err != nil { return nil, nil, err } @@ -950,7 +948,7 @@ func (s *IssueService) UpdateCommentWithContext(ctx context.Context, issueID str Body: comment.Body, } apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, comment.ID) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, reqBody) + req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, reqBody) if err != nil { return nil, nil, err } @@ -974,7 +972,7 @@ func (s *IssueService) UpdateComment(issueID string, comment *Comment) (*Comment // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-issue-issueIdOrKey-comment-id-delete func (s *IssueService) DeleteCommentWithContext(ctx context.Context, issueID, commentID string) error { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, commentID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return err } @@ -999,7 +997,7 @@ func (s *IssueService) DeleteComment(issueID, commentID string) error { // https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-worklog-post func (s *IssueService) AddWorklogRecordWithContext(ctx context.Context, issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, record) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, record) if err != nil { return nil, nil, err } @@ -1031,7 +1029,7 @@ func (s *IssueService) AddWorklogRecord(issueID string, record *WorklogRecord, o // https://docs.atlassian.com/software/jira/docs/api/REST/7.1.2/#api/2/issue-updateWorklog func (s *IssueService) UpdateWorklogRecordWithContext(ctx context.Context, issueID, worklogID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog/%s", issueID, worklogID) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, record) + req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, record) if err != nil { return nil, nil, err } @@ -1064,7 +1062,7 @@ func (s *IssueService) UpdateWorklogRecord(issueID, worklogID string, record *Wo // Caller must close resp.Body func (s *IssueService) AddLinkWithContext(ctx context.Context, issueLink *IssueLink) (*Response, error) { apiEndpoint := "rest/api/2/issueLink" - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, issueLink) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, issueLink) if err != nil { return nil, err } @@ -1115,7 +1113,7 @@ func (s *IssueService) SearchWithContext(ctx context.Context, jql string, option u.RawQuery = uv.Encode() - req, err := s.client.NewRequestWithContext(ctx, "GET", u.String(), nil) + req, err := s.client.NewRequest(ctx, "GET", u.String(), nil) if err != nil { return []Issue{}, nil, err } @@ -1185,7 +1183,7 @@ func (s *IssueService) SearchPages(jql string, options *SearchOptions, f func(Is // GetCustomFieldsWithContext returns a map of customfield_* keys with string values func (s *IssueService) GetCustomFieldsWithContext(ctx context.Context, issueID string) (CustomFields, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -1230,7 +1228,7 @@ func (s *IssueService) GetCustomFields(issueID string) (CustomFields, *Response, // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getTransitions func (s *IssueService) GetTransitionsWithContext(ctx context.Context, id string) ([]Transition, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions?expand=transitions.fields", id) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -1274,7 +1272,7 @@ func (s *IssueService) DoTransition(ticketID, transitionID string) (*Response, e func (s *IssueService) DoTransitionWithPayloadWithContext(ctx context.Context, ticketID, payload interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions", ticketID) - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, payload) if err != nil { return nil, err } @@ -1384,7 +1382,7 @@ func (s *IssueService) DeleteWithContext(ctx context.Context, issueID string) (* deletePayload["deleteSubtasks"] = "true" content, _ := json.Marshal(deletePayload) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, content) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, content) if err != nil { return nil, err } @@ -1405,7 +1403,7 @@ func (s *IssueService) Delete(issueID string) (*Response, error) { func (s *IssueService) GetWatchersWithContext(ctx context.Context, issueID string) (*[]User, *Response, error) { watchesAPIEndpoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) - req, err := s.client.NewRequestWithContext(ctx, "GET", watchesAPIEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", watchesAPIEndpoint, nil) if err != nil { return nil, nil, err } @@ -1443,7 +1441,7 @@ func (s *IssueService) GetWatchers(issueID string) (*[]User, *Response, error) { func (s *IssueService) AddWatcherWithContext(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, userName) + req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, userName) if err != nil { return nil, err } @@ -1469,7 +1467,7 @@ func (s *IssueService) AddWatcher(issueID string, userName string) (*Response, e func (s *IssueService) RemoveWatcherWithContext(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, userName) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, userName) if err != nil { return nil, err } @@ -1495,7 +1493,7 @@ func (s *IssueService) RemoveWatcher(issueID string, userName string) (*Response func (s *IssueService) UpdateAssigneeWithContext(ctx context.Context, issueID string, assignee *User) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/assignee", issueID) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndPoint, assignee) + req, err := s.client.NewRequest(ctx, "PUT", apiEndPoint, assignee) if err != nil { return nil, err } @@ -1529,7 +1527,7 @@ func (c ChangelogHistory) CreatedTime() (time.Time, error) { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getRemoteIssueLinks func (s *IssueService) GetRemoteLinksWithContext(ctx context.Context, id string) (*[]RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", id) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -1553,7 +1551,7 @@ func (s *IssueService) GetRemoteLinks(id string) (*[]RemoteLink, *Response, erro // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-remotelink-post func (s *IssueService) AddRemoteLinkWithContext(ctx context.Context, issueID string, remotelink *RemoteLink) (*RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", issueID) - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, remotelink) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, remotelink) if err != nil { return nil, nil, err } @@ -1578,7 +1576,7 @@ func (s *IssueService) AddRemoteLink(issueID string, remotelink *RemoteLink) (*R // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-remote-links/#api-rest-api-2-issue-issueidorkey-remotelink-linkid-put func (s *IssueService) UpdateRemoteLinkWithContext(ctx context.Context, issueID string, linkID int, remotelink *RemoteLink) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink/%d", issueID, linkID) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, remotelink) + req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, remotelink) if err != nil { return nil, err } diff --git a/cloud/issuelinktype.go b/cloud/issuelinktype.go index a65e88e..9d09eea 100644 --- a/cloud/issuelinktype.go +++ b/cloud/issuelinktype.go @@ -10,16 +10,14 @@ import ( // IssueLinkTypeService handles issue link types for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Issue-link-types -type IssueLinkTypeService struct { - client *Client -} +type IssueLinkTypeService service // GetListWithContext gets all of the issue link types from Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-get func (s *IssueLinkTypeService) GetListWithContext(ctx context.Context) ([]IssueLinkType, *Response, error) { apiEndpoint := "rest/api/2/issueLinkType" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -42,7 +40,7 @@ func (s *IssueLinkTypeService) GetList() ([]IssueLinkType, *Response, error) { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-get func (s *IssueLinkTypeService) GetWithContext(ctx context.Context, ID string) (*IssueLinkType, *Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) if err != nil { return nil, nil, err } @@ -65,7 +63,7 @@ func (s *IssueLinkTypeService) Get(ID string) (*IssueLinkType, *Response, error) // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-post func (s *IssueLinkTypeService) CreateWithContext(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := "/rest/api/2/issueLinkType" - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, linkType) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, linkType) if err != nil { return nil, nil, err } @@ -101,7 +99,7 @@ func (s *IssueLinkTypeService) Create(linkType *IssueLinkType) (*IssueLinkType, // Caller must close resp.Body func (s *IssueLinkTypeService) UpdateWithContext(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", linkType.ID) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, linkType) + req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, linkType) if err != nil { return nil, nil, err } @@ -125,7 +123,7 @@ func (s *IssueLinkTypeService) Update(linkType *IssueLinkType) (*IssueLinkType, // Caller must close resp.Body func (s *IssueLinkTypeService) DeleteWithContext(ctx context.Context, ID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return nil, err } diff --git a/cloud/jira.go b/cloud/jira.go index bd1894b..f4273b7 100644 --- a/cloud/jira.go +++ b/cloud/jira.go @@ -10,27 +10,37 @@ import ( "net/url" "reflect" "strings" + "sync" "github.com/google/go-querystring/query" ) -// httpClient defines an interface for an http.Client implementation so that alternative -// http Clients can be passed in for making requests -type httpClient interface { - Do(request *http.Request) (response *http.Response, err error) -} +const ( + ClientVersion = "2.0.0" + + defaultUserAgent = "go-jira" + "/" + ClientVersion +) // A Client manages communication with the Jira API. type Client struct { - // HTTP client used to communicate with the API. - client httpClient + clientMu sync.Mutex // clientMu protects the client during calls that modify it. + client *http.Client // HTTP client used to communicate with the API. // Base URL for API requests. - baseURL *url.URL + // Should be set to a domain endpoint of the Jira instance. + // BaseURL should always be specified with a trailing slash. + BaseURL *url.URL + + // User agent used when communicating with the Jira API. + UserAgent string // Session storage if the user authenticates with a Session cookie + // TODO Needed in Cloud and/or onpremise? session *Session + // Reuse a single struct instead of allocating one for each service on the heap. + common service + // Services used for talking to different parts of the Jira API. Authentication *AuthenticationService Issue *IssueService @@ -56,62 +66,77 @@ type Client struct { Request *RequestService } -// NewClient returns a new Jira API client. -// If a nil httpClient is provided, http.DefaultClient will be used. -// To use API methods which require authentication you can follow the preferred solution and -// provide an http.Client that will perform the authentication for you with OAuth and HTTP Basic (such as that provided by the golang.org/x/oauth2 library). -// As an alternative you can use Session Cookie based authentication provided by this package as well. -// See https://docs.atlassian.com/jira/REST/latest/#authentication +// service is the base structure to bundle API services +// under a sub-struct. +type service struct { + client *Client +} + +// Client returns the http.Client used by this Jira client. +func (c *Client) Client() *http.Client { + c.clientMu.Lock() + defer c.clientMu.Unlock() + clientCopy := *c.client + return &clientCopy +} + +// NewClient returns a new Jira API client with provided base URL (often is your Jira hostname) +// If a nil httpClient is provided, a new http.Client will be used. +// To use API methods which require authentication, provide an http.Client that will perform the authentication for you (such as that provided by the golang.org/x/oauth2 library). // baseURL is the HTTP endpoint of your Jira instance and should always be specified with a trailing slash. -func NewClient(httpClient httpClient, baseURL string) (*Client, error) { +func NewClient(baseURL string, httpClient *http.Client) (*Client, error) { if httpClient == nil { - httpClient = http.DefaultClient - } - - // ensure the baseURL contains a trailing slash so that all paths are preserved in later calls - if !strings.HasSuffix(baseURL, "/") { - baseURL += "/" + httpClient = &http.Client{} } - parsedBaseURL, err := url.Parse(baseURL) + baseEndpoint, err := url.Parse(baseURL) if err != nil { return nil, err } + // ensure the baseURL contains a trailing slash so that all paths are preserved in later calls + if !strings.HasSuffix(baseEndpoint.Path, "/") { + baseEndpoint.Path += "/" + } c := &Client{ - client: httpClient, - baseURL: parsedBaseURL, + client: httpClient, + BaseURL: baseEndpoint, + UserAgent: defaultUserAgent, } + c.common.client = c + + // TODO Check if the authentication service is still needed (because of the transports) c.Authentication = &AuthenticationService{client: c} - c.Issue = &IssueService{client: c} - c.Project = &ProjectService{client: c} - c.Board = &BoardService{client: c} - c.Sprint = &SprintService{client: c} - c.User = &UserService{client: c} - c.Group = &GroupService{client: c} - c.Version = &VersionService{client: c} - c.Priority = &PriorityService{client: c} - c.Field = &FieldService{client: c} - c.Component = &ComponentService{client: c} - c.Resolution = &ResolutionService{client: c} - c.StatusCategory = &StatusCategoryService{client: c} - c.Filter = &FilterService{client: c} - c.Role = &RoleService{client: c} - c.PermissionScheme = &PermissionSchemeService{client: c} - c.Status = &StatusService{client: c} - c.IssueLinkType = &IssueLinkTypeService{client: c} - c.Organization = &OrganizationService{client: c} - c.ServiceDesk = &ServiceDeskService{client: c} - c.Customer = &CustomerService{client: c} - c.Request = &RequestService{client: c} + c.Issue = (*IssueService)(&c.common) + c.Project = (*ProjectService)(&c.common) + c.Board = (*BoardService)(&c.common) + c.Sprint = (*SprintService)(&c.common) + c.User = (*UserService)(&c.common) + c.Group = (*GroupService)(&c.common) + c.Version = (*VersionService)(&c.common) + c.Priority = (*PriorityService)(&c.common) + c.Field = (*FieldService)(&c.common) + c.Component = (*ComponentService)(&c.common) + c.Resolution = (*ResolutionService)(&c.common) + c.StatusCategory = (*StatusCategoryService)(&c.common) + c.Filter = (*FilterService)(&c.common) + c.Role = (*RoleService)(&c.common) + c.PermissionScheme = (*PermissionSchemeService)(&c.common) + c.Status = (*StatusService)(&c.common) + c.IssueLinkType = (*IssueLinkTypeService)(&c.common) + c.Organization = (*OrganizationService)(&c.common) + c.ServiceDesk = (*ServiceDeskService)(&c.common) + c.Customer = (*CustomerService)(&c.common) + c.Request = (*RequestService)(&c.common) return c, nil } -// NewRawRequestWithContext creates an API request. +// TODO Do we need it? +// NewRawRequest creates an API request. // A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client. // Allows using an optional native io.Reader for sourcing the request body. -func (c *Client) NewRawRequestWithContext(ctx context.Context, method, urlStr string, body io.Reader) (*http.Request, error) { +func (c *Client) NewRawRequest(ctx context.Context, method, urlStr string, body io.Reader) (*http.Request, error) { rel, err := url.Parse(urlStr) if err != nil { return nil, err @@ -119,7 +144,7 @@ func (c *Client) NewRawRequestWithContext(ctx context.Context, method, urlStr st // Relative URLs should be specified without a preceding slash since baseURL will have the trailing slash rel.Path = strings.TrimLeft(rel.Path, "/") - u := c.baseURL.ResolveReference(rel) + u := c.BaseURL.ResolveReference(rel) req, err := http.NewRequestWithContext(ctx, method, u.String(), body) if err != nil { @@ -146,24 +171,21 @@ func (c *Client) NewRawRequestWithContext(ctx context.Context, method, urlStr st return req, nil } -// NewRawRequest wraps NewRawRequestWithContext using the background context. -func (c *Client) NewRawRequest(method, urlStr string, body io.Reader) (*http.Request, error) { - return c.NewRawRequestWithContext(context.Background(), method, urlStr, body) -} - -// NewRequestWithContext creates an API request. -// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client. +// NewRequest creates an API request. +// A relative URL can be provided in urlStr, in which case it is resolved relative to the BaseURL of the Client. // If specified, the value pointed to by body is JSON encoded and included as the request body. -func (c *Client) NewRequestWithContext(ctx context.Context, method, urlStr string, body interface{}) (*http.Request, error) { +func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body interface{}) (*http.Request, error) { rel, err := url.Parse(urlStr) if err != nil { return nil, err } - // Relative URLs should be specified without a preceding slash since baseURL will have the trailing slash + // Relative URLs should be specified without a preceding slash since BaseURL will have the trailing slash rel.Path = strings.TrimLeft(rel.Path, "/") - u := c.baseURL.ResolveReference(rel) + u := c.BaseURL.ResolveReference(rel) + // TODO This part is the difference between NewRawRequestWithContext + // Check if we can get this working in one function var buf io.ReadWriter if body != nil { buf = new(bytes.Buffer) @@ -198,15 +220,10 @@ func (c *Client) NewRequestWithContext(ctx context.Context, method, urlStr strin return req, nil } -// NewRequest wraps NewRequestWithContext using the background context. -func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) { - return c.NewRequestWithContext(context.Background(), method, urlStr, body) -} - -// addOptions adds the parameters in opt as URL query parameters to s. opt +// addOptions adds the parameters in opts as URL query parameters to s. opts // must be a struct whose fields may contain "url" tags. -func addOptions(s string, opt interface{}) (string, error) { - v := reflect.ValueOf(opt) +func addOptions(s string, opts interface{}) (string, error) { + v := reflect.ValueOf(opts) if v.Kind() == reflect.Ptr && v.IsNil() { return s, nil } @@ -216,7 +233,7 @@ func addOptions(s string, opt interface{}) (string, error) { return s, err } - qs, err := query.Values(opt) + qs, err := query.Values(opts) if err != nil { return s, err } @@ -236,7 +253,7 @@ func (c *Client) NewMultiPartRequestWithContext(ctx context.Context, method, url // Relative URLs should be specified without a preceding slash since baseURL will have the trailing slash rel.Path = strings.TrimLeft(rel.Path, "/") - u := c.baseURL.ResolveReference(rel) + u := c.BaseURL.ResolveReference(rel) req, err := http.NewRequestWithContext(ctx, method, u.String(), buf) if err != nil { @@ -307,12 +324,6 @@ func CheckResponse(r *http.Response) error { return err } -// GetBaseURL will return you the Base URL. -// This is the same URL as in the NewClient constructor -func (c *Client) GetBaseURL() url.URL { - return *c.baseURL -} - // Response represents Jira API response. It wraps http.Response returned from // API and provides information about paging. type Response struct { diff --git a/cloud/jira_test.go b/cloud/jira_test.go index 65b2272..224af7b 100644 --- a/cloud/jira_test.go +++ b/cloud/jira_test.go @@ -2,6 +2,7 @@ package cloud import ( "bytes" + "context" "fmt" "io" "net/http" @@ -36,7 +37,7 @@ func setup() { testServer = httptest.NewServer(testMux) // jira client configured to use test server - testClient, _ = NewClient(nil, testServer.URL) + testClient, _ = NewClient(testServer.URL, nil) } // teardown closes the test HTTP server. @@ -73,7 +74,7 @@ func testRequestParams(t *testing.T, r *http.Request, want map[string]string) { } func TestNewClient_WrongUrl(t *testing.T) { - c, err := NewClient(nil, "://issues.apache.org/jira/") + c, err := NewClient("://issues.apache.org/jira/", nil) if err == nil { t.Error("Expected an error. Got none") @@ -87,7 +88,7 @@ func TestNewClient_WithHttpClient(t *testing.T) { httpClient := http.DefaultClient httpClient.Timeout = 10 * time.Minute - c, err := NewClient(httpClient, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, httpClient) if err != nil { t.Errorf("Got an error: %s", err) } @@ -101,7 +102,7 @@ func TestNewClient_WithHttpClient(t *testing.T) { } func TestNewClient_WithServices(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("Got an error: %s", err) @@ -157,14 +158,14 @@ func TestCheckResponse(t *testing.T) { } func TestClient_NewRequest(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } inURL, outURL := "rest/api/2/issue/", testJiraInstanceURL+"rest/api/2/issue/" inBody, outBody := &Issue{Key: "MESOS"}, `{"key":"MESOS"}`+"\n" - req, _ := c.NewRequest("GET", inURL, inBody) + req, _ := c.NewRequest(context.Background(), "GET", inURL, inBody) // Test that relative URL was expanded if got, want := req.URL.String(), outURL; got != want { @@ -179,7 +180,7 @@ func TestClient_NewRequest(t *testing.T) { } func TestClient_NewRawRequest(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } @@ -188,7 +189,7 @@ func TestClient_NewRawRequest(t *testing.T) { outBody := `{"key":"MESOS"}` + "\n" inBody := outBody - req, _ := c.NewRawRequest("GET", inURL, strings.NewReader(outBody)) + req, _ := c.NewRawRequest(context.Background(), "GET", inURL, strings.NewReader(outBody)) // Test that relative URL was expanded if got, want := req.URL.String(), outURL; got != want { @@ -212,16 +213,16 @@ func testURLParseError(t *testing.T, err error) { } func TestClient_NewRequest_BadURL(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } - _, err = c.NewRequest("GET", ":", nil) + _, err = c.NewRequest(context.Background(), "GET", ":", nil) testURLParseError(t, err) } func TestClient_NewRequest_SessionCookies(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } @@ -232,7 +233,7 @@ func TestClient_NewRequest_SessionCookies(t *testing.T) { inURL := "rest/api/2/issue/" inBody := &Issue{Key: "MESOS"} - req, err := c.NewRequest("GET", inURL, inBody) + req, err := c.NewRequest(context.Background(), "GET", inURL, inBody) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) @@ -250,7 +251,7 @@ func TestClient_NewRequest_SessionCookies(t *testing.T) { } func TestClient_NewRequest_BasicAuth(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } @@ -259,7 +260,7 @@ func TestClient_NewRequest_BasicAuth(t *testing.T) { inURL := "rest/api/2/issue/" inBody := &Issue{Key: "MESOS"} - req, err := c.NewRequest("GET", inURL, inBody) + req, err := c.NewRequest(context.Background(), "GET", inURL, inBody) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) @@ -276,11 +277,11 @@ func TestClient_NewRequest_BasicAuth(t *testing.T) { // since there is no difference between an HTTP request body that is an empty string versus one that is not set at all. // However in certain cases, intermediate systems may treat these differently resulting in subtle errors. func TestClient_NewRequest_EmptyBody(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } - req, err := c.NewRequest("GET", "/", nil) + req, err := c.NewRequest(context.Background(), "GET", "/", nil) if err != nil { t.Fatalf("NewRequest returned unexpected error: %v", err) } @@ -290,7 +291,7 @@ func TestClient_NewRequest_EmptyBody(t *testing.T) { } func TestClient_NewMultiPartRequest(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } @@ -323,7 +324,7 @@ func TestClient_NewMultiPartRequest(t *testing.T) { } func TestClient_NewMultiPartRequest_BasicAuth(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } @@ -363,7 +364,7 @@ func TestClient_Do(t *testing.T) { fmt.Fprint(w, `{"A":"a"}`) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) body := new(foo) testClient.Do(req, body) @@ -384,7 +385,7 @@ func TestClient_Do_HTTPResponse(t *testing.T) { fmt.Fprint(w, `{"A":"a"}`) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) res, _ := testClient.Do(req, nil) _, err := io.ReadAll(res.Body) @@ -403,7 +404,7 @@ func TestClient_Do_HTTPError(t *testing.T) { http.Error(w, "Bad Request", 400) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) _, err := testClient.Do(req, nil) if err == nil { @@ -421,7 +422,7 @@ func TestClient_Do_RedirectLoop(t *testing.T) { http.Redirect(w, r, "/", http.StatusFound) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) _, err := testClient.Do(req, nil) if err == nil { @@ -431,22 +432,3 @@ func TestClient_Do_RedirectLoop(t *testing.T) { t.Errorf("Expected a URL error; got %+v.", err) } } - -func TestClient_GetBaseURL_WithURL(t *testing.T) { - u, err := url.Parse(testJiraInstanceURL) - if err != nil { - t.Errorf("URL parsing -> Got an error: %s", err) - } - - c, err := NewClient(nil, testJiraInstanceURL) - if err != nil { - t.Errorf("Client creation -> Got an error: %s", err) - } - if c == nil { - t.Error("Expected a client. Got none") - } - - if b := c.GetBaseURL(); !reflect.DeepEqual(b, *u) { - t.Errorf("Base URLs are not equal. Expected %+v, got %+v", *u, b) - } -} diff --git a/cloud/metaissue.go b/cloud/metaissue.go index 40f0ae6..438a5f3 100644 --- a/cloud/metaissue.go +++ b/cloud/metaissue.go @@ -62,7 +62,7 @@ func (s *IssueService) GetCreateMeta(projectkeys string) (*CreateMetaInfo, *Resp func (s *IssueService) GetCreateMetaWithOptionsWithContext(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { apiEndpoint := "rest/api/2/issue/createmeta" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -93,7 +93,7 @@ func (s *IssueService) GetCreateMetaWithOptions(options *GetQueryOptions) (*Crea func (s *IssueService) GetEditMetaWithContext(ctx context.Context, issue *Issue) (*EditMetaInfo, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/issue/%s/editmeta", issue.Key) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/organization.go b/cloud/organization.go index 4950f2b..515f6c2 100644 --- a/cloud/organization.go +++ b/cloud/organization.go @@ -8,9 +8,7 @@ import ( // OrganizationService handles Organizations for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/ -type OrganizationService struct { - client *Client -} +type OrganizationService service // OrganizationCreationDTO is DTO for creat organization API type OrganizationCreationDTO struct { @@ -68,7 +66,7 @@ func (s *OrganizationService) GetAllOrganizationsWithContext(ctx context.Context apiEndPoint += fmt.Sprintf("&accountId=%s", accountID) } - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -101,7 +99,7 @@ func (s *OrganizationService) CreateOrganizationWithContext(ctx context.Context, Name: name, } - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, organization) + req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, organization) req.Header.Set("Accept", "application/json") if err != nil { @@ -133,7 +131,7 @@ func (s *OrganizationService) CreateOrganization(name string) (*Organization, *R func (s *OrganizationService) GetOrganizationWithContext(ctx context.Context, organizationID int) (*Organization, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -165,7 +163,7 @@ func (s *OrganizationService) GetOrganization(organizationID int) (*Organization func (s *OrganizationService) DeleteOrganizationWithContext(ctx context.Context, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) if err != nil { return nil, err @@ -195,7 +193,7 @@ func (s *OrganizationService) DeleteOrganization(organizationID int) (*Response, func (s *OrganizationService) GetPropertiesKeysWithContext(ctx context.Context, organizationID int) (*PropertyKeys, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property", organizationID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -225,7 +223,7 @@ func (s *OrganizationService) GetPropertiesKeys(organizationID int) (*PropertyKe func (s *OrganizationService) GetPropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*EntityProperty, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -256,7 +254,7 @@ func (s *OrganizationService) GetProperty(organizationID int, propertyKey string func (s *OrganizationService) SetPropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "PUT", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -285,7 +283,7 @@ func (s *OrganizationService) SetProperty(organizationID int, propertyKey string func (s *OrganizationService) DeletePropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -317,7 +315,7 @@ func (s *OrganizationService) DeleteProperty(organizationID int, propertyKey str func (s *OrganizationService) GetUsersWithContext(ctx context.Context, organizationID int, start int, limit int) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user?start=%d&limit=%d", organizationID, start, limit) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -346,7 +344,7 @@ func (s *OrganizationService) GetUsers(organizationID int, start int, limit int) func (s *OrganizationService) AddUsersWithContext(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, users) + req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, users) if err != nil { return nil, err @@ -374,7 +372,7 @@ func (s *OrganizationService) AddUsers(organizationID int, users OrganizationUse func (s *OrganizationService) RemoveUsersWithContext(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { diff --git a/cloud/permissionscheme.go b/cloud/permissionscheme.go index 3c11805..3f8edcc 100644 --- a/cloud/permissionscheme.go +++ b/cloud/permissionscheme.go @@ -8,9 +8,8 @@ import ( // PermissionSchemeService handles permissionschemes for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Permissionscheme -type PermissionSchemeService struct { - client *Client -} +type PermissionSchemeService service + type PermissionSchemes struct { PermissionSchemes []PermissionScheme `json:"permissionSchemes" structs:"permissionSchemes"` } @@ -33,7 +32,7 @@ type Holder struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-get func (s *PermissionSchemeService) GetListWithContext(ctx context.Context) (*PermissionSchemes, *Response, error) { apiEndpoint := "/rest/api/3/permissionscheme" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -58,7 +57,7 @@ func (s *PermissionSchemeService) GetList() (*PermissionSchemes, *Response, erro // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-schemeId-get func (s *PermissionSchemeService) GetWithContext(ctx context.Context, schemeID int) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/3/permissionscheme/%d", schemeID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/priority.go b/cloud/priority.go index 2cd5971..7f62cd4 100644 --- a/cloud/priority.go +++ b/cloud/priority.go @@ -5,9 +5,7 @@ import "context" // PriorityService handles priorities for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Priority -type PriorityService struct { - client *Client -} +type PriorityService service // Priority represents a priority of a Jira issue. // Typical types are "Normal", "Moderate", "Urgent", ... @@ -25,7 +23,7 @@ type Priority struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-priority-get func (s *PriorityService) GetListWithContext(ctx context.Context) ([]Priority, *Response, error) { apiEndpoint := "rest/api/2/priority" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/project.go b/cloud/project.go index e67fd02..60e4eae 100644 --- a/cloud/project.go +++ b/cloud/project.go @@ -10,9 +10,7 @@ import ( // ProjectService handles projects for the Jira instance / API. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project -type ProjectService struct { - client *Client -} +type ProjectService service // ProjectList represent a list of Projects type ProjectList []struct { @@ -99,7 +97,7 @@ func (s *ProjectService) GetList() (*ProjectList, *Response, error) { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects func (s *ProjectService) ListWithOptionsWithContext(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { apiEndpoint := "rest/api/2/project" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -134,7 +132,7 @@ func (s *ProjectService) ListWithOptions(options *GetQueryOptions) (*ProjectList // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject func (s *ProjectService) GetWithContext(ctx context.Context, projectID string) (*Project, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/project/%s", projectID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -161,7 +159,7 @@ func (s *ProjectService) Get(projectID string) (*Project, *Response, error) { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject func (s *ProjectService) GetPermissionSchemeWithContext(ctx context.Context, projectID string) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s/permissionscheme", projectID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/request.go b/cloud/request.go index 2a0868a..6d276ee 100644 --- a/cloud/request.go +++ b/cloud/request.go @@ -6,9 +6,7 @@ import ( ) // RequestService handles ServiceDesk customer requests for the Jira instance / API. -type RequestService struct { - client *Client -} +type RequestService service // Request represents a ServiceDesk customer request. type Request struct { @@ -78,7 +76,7 @@ func (r *RequestService) CreateWithContext(ctx context.Context, requester string payload.FieldValues[field.FieldID] = field.Value } - req, err := r.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload) + req, err := r.client.NewRequest(ctx, "POST", apiEndpoint, payload) if err != nil { return nil, nil, err } @@ -103,7 +101,7 @@ func (r *RequestService) Create(requester string, participants []string, request func (r *RequestService) CreateCommentWithContext(ctx context.Context, issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/request/%v/comment", issueIDOrKey) - req, err := r.client.NewRequestWithContext(ctx, "POST", apiEndpoint, comment) + req, err := r.client.NewRequest(ctx, "POST", apiEndpoint, comment) if err != nil { return nil, nil, err } diff --git a/cloud/resolution.go b/cloud/resolution.go index df5282e..b10e44d 100644 --- a/cloud/resolution.go +++ b/cloud/resolution.go @@ -5,9 +5,7 @@ import "context" // ResolutionService handles resolutions for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Resolution -type ResolutionService struct { - client *Client -} +type ResolutionService service // Resolution represents a resolution of a Jira issue. // Typical types are "Fixed", "Suspended", "Won't Fix", ... @@ -23,7 +21,7 @@ type Resolution struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-resolution-get func (s *ResolutionService) GetListWithContext(ctx context.Context) ([]Resolution, *Response, error) { apiEndpoint := "rest/api/2/resolution" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/role.go b/cloud/role.go index 6ebc71b..7854bf3 100644 --- a/cloud/role.go +++ b/cloud/role.go @@ -8,9 +8,7 @@ import ( // RoleService handles roles for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Role -type RoleService struct { - client *Client -} +type RoleService service // Role represents a Jira product role type Role struct { @@ -41,7 +39,7 @@ type ActorUser struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-get func (s *RoleService) GetListWithContext(ctx context.Context) (*[]Role, *Response, error) { apiEndpoint := "rest/api/3/role" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -64,7 +62,7 @@ func (s *RoleService) GetList() (*[]Role, *Response, error) { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-id-get func (s *RoleService) GetWithContext(ctx context.Context, roleID int) (*Role, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/3/role/%d", roleID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/servicedesk.go b/cloud/servicedesk.go index 8ee6c04..69df04b 100644 --- a/cloud/servicedesk.go +++ b/cloud/servicedesk.go @@ -10,9 +10,7 @@ import ( ) // ServiceDeskService handles ServiceDesk for the Jira instance / API. -type ServiceDeskService struct { - client *Client -} +type ServiceDeskService service // ServiceDeskOrganizationDTO is a DTO for ServiceDesk organizations type ServiceDeskOrganizationDTO struct { @@ -29,7 +27,7 @@ func (s *ServiceDeskService) GetOrganizationsWithContext(ctx context.Context, se apiEndPoint += fmt.Sprintf("&accountId=%s", accountID) } - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -65,7 +63,7 @@ func (s *ServiceDeskService) AddOrganizationWithContext(ctx context.Context, ser OrganizationID: organizationID, } - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, organization) + req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, organization) if err != nil { return nil, err @@ -100,7 +98,7 @@ func (s *ServiceDeskService) RemoveOrganizationWithContext(ctx context.Context, OrganizationID: organizationID, } - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, organization) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, organization) if err != nil { return nil, err @@ -132,7 +130,7 @@ func (s *ServiceDeskService) AddCustomersWithContext(ctx context.Context, servic }{ AccountIDs: acountIDs, } - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, payload) if err != nil { return nil, err } @@ -164,7 +162,7 @@ func (s *ServiceDeskService) RemoveCustomersWithContext(ctx context.Context, ser }{ AccountIDs: acountIDs, } - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, payload) if err != nil { return nil, err } @@ -190,7 +188,7 @@ func (s *ServiceDeskService) RemoveCustomers(serviceDeskID interface{}, acountID // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-get func (s *ServiceDeskService) ListCustomersWithContext(ctx context.Context, serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/sprint.go b/cloud/sprint.go index e68338c..e589159 100644 --- a/cloud/sprint.go +++ b/cloud/sprint.go @@ -9,9 +9,7 @@ import ( // SprintService handles sprints in Jira Agile API. // See https://docs.atlassian.com/jira-software/REST/cloud/ -type SprintService struct { - client *Client -} +type SprintService service // IssuesWrapper represents a wrapper struct for moving issues to sprint type IssuesWrapper struct { @@ -34,7 +32,7 @@ func (s *SprintService) MoveIssuesToSprintWithContext(ctx context.Context, sprin payload := IssuesWrapper{Issues: issueIDs} - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, payload) if err != nil { return nil, err @@ -61,7 +59,7 @@ func (s *SprintService) MoveIssuesToSprint(sprintID int, issueIDs []string) (*Re func (s *SprintService) GetIssuesForSprintWithContext(ctx context.Context, sprintID int) ([]Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err @@ -94,7 +92,7 @@ func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, er func (s *SprintService) GetIssueWithContext(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/issue/%s", issueID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err diff --git a/cloud/status.go b/cloud/status.go index 0480d56..181d8ac 100644 --- a/cloud/status.go +++ b/cloud/status.go @@ -5,9 +5,7 @@ import "context" // StatusService handles staties for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Workflow-statuses -type StatusService struct { - client *Client -} +type StatusService service // Status represents the current status of a Jira issue. // Typical status are "Open", "In Progress", "Closed", ... @@ -26,7 +24,7 @@ type Status struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-status-get func (s *StatusService) GetAllStatusesWithContext(ctx context.Context) ([]Status, *Response, error) { apiEndpoint := "rest/api/2/status" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err diff --git a/cloud/statuscategory.go b/cloud/statuscategory.go index c78b03b..d7da81c 100644 --- a/cloud/statuscategory.go +++ b/cloud/statuscategory.go @@ -5,9 +5,7 @@ import "context" // StatusCategoryService handles status categories for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Statuscategory -type StatusCategoryService struct { - client *Client -} +type StatusCategoryService service // StatusCategory represents the category a status belongs to. // Those categories can be user defined in every Jira instance. @@ -32,7 +30,7 @@ const ( // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get func (s *StatusCategoryService) GetListWithContext(ctx context.Context) ([]StatusCategory, *Response, error) { apiEndpoint := "rest/api/2/statuscategory" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/user.go b/cloud/user.go index 289ef08..acd038c 100644 --- a/cloud/user.go +++ b/cloud/user.go @@ -10,9 +10,7 @@ import ( // UserService handles users for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Users -type UserService struct { - client *Client -} +type UserService service // User represents a Jira user. type User struct { @@ -51,7 +49,7 @@ type userSearchF func(userSearch) userSearch // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-get func (s *UserService) GetWithContext(ctx context.Context, accountId string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -75,7 +73,7 @@ func (s *UserService) Get(accountId string) (*User, *Response, error) { // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser func (s *UserService) GetByAccountIDWithContext(ctx context.Context, accountID string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -98,7 +96,7 @@ func (s *UserService) GetByAccountID(accountID string) (*User, *Response, error) // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-createUser func (s *UserService) CreateWithContext(ctx context.Context, user *User) (*User, *Response, error) { apiEndpoint := "/rest/api/2/user" - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, user) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, user) if err != nil { return nil, nil, err } @@ -135,7 +133,7 @@ func (s *UserService) Create(user *User) (*User, *Response, error) { // Caller must close resp.Body func (s *UserService) DeleteWithContext(ctx context.Context, accountId string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return nil, err } @@ -158,7 +156,7 @@ func (s *UserService) Delete(accountId string) (*Response, error) { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-groups-get func (s *UserService) GetGroupsWithContext(ctx context.Context, accountId string) (*[]UserGroup, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?accountId=%s", accountId) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -181,7 +179,7 @@ func (s *UserService) GetGroups(accountId string) (*[]UserGroup, *Response, erro // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-myself-get func (s *UserService) GetSelfWithContext(ctx context.Context) (*User, *Response, error) { const apiEndpoint = "rest/api/2/myself" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -275,7 +273,7 @@ func (s *UserService) FindWithContext(ctx context.Context, property string, twea } apiEndpoint := fmt.Sprintf("/rest/api/2/user/search?%s", queryString[:len(queryString)-1]) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/version.go b/cloud/version.go index e10cc89..69b4bac 100644 --- a/cloud/version.go +++ b/cloud/version.go @@ -10,9 +10,7 @@ import ( // VersionService handles Versions for the Jira instance / API. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/version -type VersionService struct { - client *Client -} +type VersionService service // Version represents a single release version of a project type Version struct { @@ -33,7 +31,7 @@ type Version struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-get func (s *VersionService) GetWithContext(ctx context.Context, versionID int) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/version/%v", versionID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -56,7 +54,7 @@ func (s *VersionService) Get(versionID int) (*Version, *Response, error) { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-post func (s *VersionService) CreateWithContext(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := "/rest/api/2/version" - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, version) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, version) if err != nil { return nil, nil, err } @@ -92,7 +90,7 @@ func (s *VersionService) Create(version *Version) (*Version, *Response, error) { // Caller must close resp.Body func (s *VersionService) UpdateWithContext(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/version/%v", version.ID) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, version) + req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, version) if err != nil { return nil, nil, err } diff --git a/onpremise/auth_transport_basic_test.go b/onpremise/auth_transport_basic_test.go index 37918f6..e85cf27 100644 --- a/onpremise/auth_transport_basic_test.go +++ b/onpremise/auth_transport_basic_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "net/http" "testing" ) @@ -29,8 +30,8 @@ func TestBasicAuthTransport(t *testing.T) { Password: password, } - basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) - req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) + req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) basicAuthClient.Do(req, nil) } diff --git a/onpremise/auth_transport_cookie.go b/onpremise/auth_transport_cookie.go index 3b76813..c63104e 100644 --- a/onpremise/auth_transport_cookie.go +++ b/onpremise/auth_transport_cookie.go @@ -89,6 +89,7 @@ func (t *CookieAuthTransport) buildAuthRequest() (*http.Request, error) { b := new(bytes.Buffer) json.NewEncoder(b).Encode(body) + // TODO Use a context here req, err := http.NewRequest("POST", t.AuthURL, b) if err != nil { return nil, err diff --git a/onpremise/auth_transport_cookie_test.go b/onpremise/auth_transport_cookie_test.go index 19a33bc..dbf6b2b 100644 --- a/onpremise/auth_transport_cookie_test.go +++ b/onpremise/auth_transport_cookie_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "net/http" "net/http/httptest" "testing" @@ -36,8 +37,8 @@ func TestCookieAuthTransport_SessionObject_Exists(t *testing.T) { SessionObject: []*http.Cookie{testCookie}, } - basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) - req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) + req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) basicAuthClient.Do(req, nil) } @@ -72,8 +73,8 @@ func TestCookieAuthTransport_SessionObject_ExistsWithEmptyCookie(t *testing.T) { SessionObject: []*http.Cookie{emptyCookie, testCookie}, } - basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) - req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) + req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) basicAuthClient.Do(req, nil) } @@ -113,7 +114,7 @@ func TestCookieAuthTransport_SessionObject_DoesNotExist(t *testing.T) { AuthURL: ts.URL, } - basicAuthClient, _ := NewClient(tp.Client(), testServer.URL) - req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) + req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) basicAuthClient.Do(req, nil) } diff --git a/onpremise/auth_transport_jwt_test.go b/onpremise/auth_transport_jwt_test.go index ccf308b..6332d54 100644 --- a/onpremise/auth_transport_jwt_test.go +++ b/onpremise/auth_transport_jwt_test.go @@ -26,6 +26,6 @@ func TestJWTAuthTransport_HeaderContainsJWT(t *testing.T) { } }) - jwtClient, _ := NewClient(jwtTransport.Client(), testServer.URL) + jwtClient, _ := NewClient(testServer.URL, jwtTransport.Client()) jwtClient.Issue.Get("TEST-1", nil) } diff --git a/onpremise/auth_transport_personal_access_token_test.go b/onpremise/auth_transport_personal_access_token_test.go index b0ac50f..b205a5f 100644 --- a/onpremise/auth_transport_personal_access_token_test.go +++ b/onpremise/auth_transport_personal_access_token_test.go @@ -23,7 +23,7 @@ func TestPATAuthTransport_HeaderContainsAuth(t *testing.T) { } }) - client, _ := NewClient(patTransport.Client(), testServer.URL) + client, _ := NewClient(testServer.URL, patTransport.Client()) client.User.GetSelf() } diff --git a/onpremise/authentication.go b/onpremise/authentication.go index 4bca28a..baade93 100644 --- a/onpremise/authentication.go +++ b/onpremise/authentication.go @@ -67,7 +67,7 @@ func (s *AuthenticationService) AcquireSessionCookieWithContext(ctx context.Cont password, } - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, body) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, body) if err != nil { return false, err } @@ -133,7 +133,7 @@ func (s *AuthenticationService) LogoutWithContext(ctx context.Context) error { } apiEndpoint := "rest/auth/1/session" - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return fmt.Errorf("creating the request to log the user out failed : %s", err) } @@ -174,7 +174,7 @@ func (s *AuthenticationService) GetCurrentUserWithContext(ctx context.Context) ( } apiEndpoint := "rest/auth/1/session" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, fmt.Errorf("could not create request for getting user info : %s", err) } diff --git a/onpremise/board.go b/onpremise/board.go index 52cbfa6..d85e0a0 100644 --- a/onpremise/board.go +++ b/onpremise/board.go @@ -10,9 +10,7 @@ import ( // BoardService handles Agile Boards for the Jira instance / API. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/server/ -type BoardService struct { - client *Client -} +type BoardService service // BoardsList reflects a list of agile boards type BoardsList struct { @@ -136,7 +134,7 @@ func (s *BoardService) GetAllBoardsWithContext(ctx context.Context, opt *BoardLi if err != nil { return nil, nil, err } - req, err := s.client.NewRequestWithContext(ctx, "GET", url, nil) + req, err := s.client.NewRequest(ctx, "GET", url, nil) if err != nil { return nil, nil, err } @@ -162,7 +160,7 @@ func (s *BoardService) GetAllBoards(opt *BoardListOptions) (*BoardsList, *Respon // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard func (s *BoardService) GetBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -192,7 +190,7 @@ func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) { // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard func (s *BoardService) CreateBoardWithContext(ctx context.Context, board *Board) (*Board, *Response, error) { apiEndpoint := "rest/agile/1.0/board" - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, board) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, board) if err != nil { return nil, nil, err } @@ -218,7 +216,7 @@ func (s *BoardService) CreateBoard(board *Board) (*Board, *Response, error) { // Caller must close resp.Body func (s *BoardService) DeleteBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -269,7 +267,7 @@ func (s *BoardService) GetAllSprintsWithOptionsWithContext(ctx context.Context, if err != nil { return nil, nil, err } - req, err := s.client.NewRequestWithContext(ctx, "GET", url, nil) + req, err := s.client.NewRequest(ctx, "GET", url, nil) if err != nil { return nil, nil, err } @@ -293,7 +291,7 @@ func (s *BoardService) GetAllSprintsWithOptions(boardID int, options *GetAllSpri func (s *BoardService) GetBoardConfigurationWithContext(ctx context.Context, boardID int) (*BoardConfiguration, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/configuration", boardID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err diff --git a/onpremise/component.go b/onpremise/component.go index 7c918dd..c1ac05d 100644 --- a/onpremise/component.go +++ b/onpremise/component.go @@ -4,9 +4,7 @@ import "context" // ComponentService handles components for the Jira instance / API.// // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.1/#api/2/component -type ComponentService struct { - client *Client -} +type ComponentService service // CreateComponentOptions are passed to the ComponentService.Create function to create a new Jira component type CreateComponentOptions struct { @@ -23,7 +21,7 @@ type CreateComponentOptions struct { // CreateWithContext creates a new Jira component based on the given options. func (s *ComponentService) CreateWithContext(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) { apiEndpoint := "rest/api/2/component" - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, options) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, options) if err != nil { return nil, nil, err } diff --git a/onpremise/customer.go b/onpremise/customer.go index 82a46a0..21e9013 100644 --- a/onpremise/customer.go +++ b/onpremise/customer.go @@ -6,9 +6,7 @@ import ( ) // CustomerService handles ServiceDesk customers for the Jira instance / API. -type CustomerService struct { - client *Client -} +type CustomerService service // Customer represents a ServiceDesk customer. type Customer struct { @@ -52,7 +50,7 @@ func (c *CustomerService) CreateWithContext(ctx context.Context, email, displayN DisplayName: displayName, } - req, err := c.client.NewRequestWithContext(ctx, http.MethodPost, apiEndpoint, payload) + req, err := c.client.NewRequest(ctx, http.MethodPost, apiEndpoint, payload) if err != nil { return nil, nil, err } diff --git a/onpremise/error_test.go b/onpremise/error_test.go index 4238f64..4aca07d 100644 --- a/onpremise/error_test.go +++ b/onpremise/error_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "errors" "fmt" "net/http" @@ -17,7 +18,7 @@ func TestError_NewJiraError(t *testing.T) { fmt.Fprint(w, `{"errorMessages":["Issue does not exist or you do not have permission to see it."],"errors":{}}`) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, errors.New("Original http error")) @@ -51,7 +52,7 @@ func TestError_NoJSON(t *testing.T) { fmt.Fprint(w, `Original message body`) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, errors.New("Original http error")) @@ -71,7 +72,7 @@ func TestError_Unauthorized_NilError(t *testing.T) { fmt.Fprint(w, `User is not authorized`) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, nil) @@ -90,7 +91,7 @@ func TestError_BadJSON(t *testing.T) { fmt.Fprint(w, `Not JSON`) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, errors.New("Original http error")) diff --git a/onpremise/examples/addlabel/main.go b/onpremise/examples/addlabel/main.go index 2f0c30a..ca5a6f7 100644 --- a/onpremise/examples/addlabel/main.go +++ b/onpremise/examples/addlabel/main.go @@ -8,7 +8,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/cloud" "golang.org/x/term" ) @@ -38,7 +38,7 @@ func main() { Password: strings.TrimSpace(password), } - client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) if err != nil { fmt.Printf("\nerror: %v\n", err) return diff --git a/onpremise/examples/basicauth/main.go b/onpremise/examples/basicauth/main.go index 8995269..7221d87 100644 --- a/onpremise/examples/basicauth/main.go +++ b/onpremise/examples/basicauth/main.go @@ -9,7 +9,7 @@ import ( "golang.org/x/term" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/cloud" ) func main() { @@ -30,7 +30,7 @@ func main() { Password: strings.TrimSpace(password), } - client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) if err != nil { fmt.Printf("\nerror: %v\n", err) return diff --git a/onpremise/examples/create/main.go b/onpremise/examples/create/main.go index b306760..2dc3837 100644 --- a/onpremise/examples/create/main.go +++ b/onpremise/examples/create/main.go @@ -7,7 +7,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/cloud" "golang.org/x/term" ) @@ -29,7 +29,7 @@ func main() { Password: strings.TrimSpace(password), } - client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) if err != nil { fmt.Printf("\nerror: %v\n", err) return diff --git a/onpremise/examples/createwithcustomfields/main.go b/onpremise/examples/createwithcustomfields/main.go index be125c7..68b32b5 100644 --- a/onpremise/examples/createwithcustomfields/main.go +++ b/onpremise/examples/createwithcustomfields/main.go @@ -7,7 +7,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/cloud" "github.com/trivago/tgo/tcontainer" "golang.org/x/term" ) @@ -36,7 +36,7 @@ func main() { Password: strings.TrimSpace(password), } - client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) if err != nil { fmt.Printf("\nerror: %v\n", err) os.Exit(1) diff --git a/onpremise/examples/do/main.go b/onpremise/examples/do/main.go index 56f0210..eee2747 100644 --- a/onpremise/examples/do/main.go +++ b/onpremise/examples/do/main.go @@ -1,14 +1,15 @@ package main import ( + "context" "fmt" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/cloud" ) func main() { - jiraClient, _ := jira.NewClient(nil, "https://jira.atlassian.com/") - req, _ := jiraClient.NewRequest("GET", "/rest/api/2/project", nil) + jiraClient, _ := jira.NewClient("https://jira.atlassian.com/", nil) + req, _ := jiraClient.NewRequest(context.Background(), "GET", "/rest/api/2/project", nil) projects := new([]jira.Project) res, err := jiraClient.Do(req, projects) diff --git a/onpremise/examples/ignorecerts/main.go b/onpremise/examples/ignorecerts/main.go index 76e42dc..525cd51 100644 --- a/onpremise/examples/ignorecerts/main.go +++ b/onpremise/examples/ignorecerts/main.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/cloud" ) func main() { @@ -14,7 +14,7 @@ func main() { } client := &http.Client{Transport: tr} - jiraClient, _ := jira.NewClient(client, "https://issues.apache.org/jira/") + jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", client) issue, _, _ := jiraClient.Issue.Get("MESOS-3325", nil) fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary) diff --git a/onpremise/examples/jql/main.go b/onpremise/examples/jql/main.go index 1a78fe9..ce3ba45 100644 --- a/onpremise/examples/jql/main.go +++ b/onpremise/examples/jql/main.go @@ -3,11 +3,11 @@ package main import ( "fmt" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/cloud" ) func main() { - jiraClient, _ := jira.NewClient(nil, "https://issues.apache.org/jira/") + jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", nil) // Running JQL query diff --git a/onpremise/examples/newclient/main.go b/onpremise/examples/newclient/main.go index b63a30f..c03461d 100644 --- a/onpremise/examples/newclient/main.go +++ b/onpremise/examples/newclient/main.go @@ -3,11 +3,11 @@ package main import ( "fmt" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/cloud" ) func main() { - jiraClient, _ := jira.NewClient(nil, "https://issues.apache.org/jira/") + jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", nil) issue, _, _ := jiraClient.Issue.Get("MESOS-3325", nil) fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary) diff --git a/onpremise/examples/pagination/main.go b/onpremise/examples/pagination/main.go index f60642a..07c52ee 100644 --- a/onpremise/examples/pagination/main.go +++ b/onpremise/examples/pagination/main.go @@ -3,7 +3,7 @@ package main import ( "fmt" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/cloud" ) // GetAllIssues will implement pagination of api and get all the issues. @@ -38,7 +38,7 @@ func GetAllIssues(client *jira.Client, searchString string) ([]jira.Issue, error } func main() { - jiraClient, err := jira.NewClient(nil, "https://issues.apache.org/jira/") + jiraClient, err := jira.NewClient("https://issues.apache.org/jira/", nil) if err != nil { panic(err) } diff --git a/onpremise/examples/renderedfields/main.go b/onpremise/examples/renderedfields/main.go index 45cdcee..274498f 100644 --- a/onpremise/examples/renderedfields/main.go +++ b/onpremise/examples/renderedfields/main.go @@ -10,7 +10,7 @@ import ( "golang.org/x/term" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/cloud" ) func main() { @@ -43,7 +43,7 @@ func main() { tp = ba.Client() } - client, err := jira.NewClient(tp, strings.TrimSpace(jiraURL)) + client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp) if err != nil { fmt.Printf("\nerror: %v\n", err) return diff --git a/onpremise/examples/searchpages/main.go b/onpremise/examples/searchpages/main.go index 8ba33ad..51db937 100644 --- a/onpremise/examples/searchpages/main.go +++ b/onpremise/examples/searchpages/main.go @@ -9,7 +9,7 @@ import ( "syscall" "time" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/cloud" "golang.org/x/term" ) @@ -34,7 +34,7 @@ func main() { Password: strings.TrimSpace(password), } - client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraURL)) + client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) if err != nil { log.Fatal(err) } diff --git a/onpremise/field.go b/onpremise/field.go index 166b9c4..7747721 100644 --- a/onpremise/field.go +++ b/onpremise/field.go @@ -5,9 +5,7 @@ import "context" // FieldService handles fields for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Field -type FieldService struct { - client *Client -} +type FieldService service // Field represents a field of a Jira issue. type Field struct { @@ -36,7 +34,7 @@ type FieldSchema struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get func (s *FieldService) GetListWithContext(ctx context.Context) ([]Field, *Response, error) { apiEndpoint := "rest/api/2/field" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/filter.go b/onpremise/filter.go index 07ead72..4652429 100644 --- a/onpremise/filter.go +++ b/onpremise/filter.go @@ -10,9 +10,7 @@ import ( // FilterService handles fields for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Filter -type FilterService struct { - client *Client -} +type FilterService service // Filter represents a Filter in Jira type Filter struct { @@ -125,7 +123,7 @@ func (fs *FilterService) GetListWithContext(ctx context.Context) ([]*Filter, *Re options := &GetQueryOptions{} apiEndpoint := "rest/api/2/filter" - req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -153,7 +151,7 @@ func (fs *FilterService) GetList() ([]*Filter, *Response, error) { // GetFavouriteListWithContext retrieves the user's favourited filters from Jira func (fs *FilterService) GetFavouriteListWithContext(ctx context.Context) ([]*Filter, *Response, error) { apiEndpoint := "rest/api/2/filter/favourite" - req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -174,7 +172,7 @@ func (fs *FilterService) GetFavouriteList() ([]*Filter, *Response, error) { // GetWithContext retrieves a single Filter from Jira func (fs *FilterService) GetWithContext(ctx context.Context, filterID int) (*Filter, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/filter/%d", filterID) - req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -202,7 +200,7 @@ func (fs *FilterService) GetMyFiltersWithContext(ctx context.Context, opts *GetM if err != nil { return nil, nil, err } - req, err := fs.client.NewRequestWithContext(ctx, "GET", url, nil) + req, err := fs.client.NewRequest(ctx, "GET", url, nil) if err != nil { return nil, nil, err } @@ -230,7 +228,7 @@ func (fs *FilterService) SearchWithContext(ctx context.Context, opt *FilterSearc if err != nil { return nil, nil, err } - req, err := fs.client.NewRequestWithContext(ctx, "GET", url, nil) + req, err := fs.client.NewRequest(ctx, "GET", url, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/group.go b/onpremise/group.go index e197922..12a0cf5 100644 --- a/onpremise/group.go +++ b/onpremise/group.go @@ -9,9 +9,7 @@ import ( // GroupService handles Groups for the Jira instance / API. // // Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group -type GroupService struct { - client *Client -} +type GroupService service // groupMembersResult is only a small wrapper around the Group* methods // to be able to parse the results @@ -68,7 +66,7 @@ type GroupSearchOptions struct { // WARNING: This API only returns the first page of group members func (s *GroupService) GetWithContext(ctx context.Context, name string) ([]GroupMember, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -105,7 +103,7 @@ func (s *GroupService) GetWithOptionsWithContext(ctx context.Context, name strin options.IncludeInactiveUsers, ) } - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -132,7 +130,7 @@ func (s *GroupService) AddWithContext(ctx context.Context, groupname string, use Name string `json:"name"` } user.Name = username - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, &user) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, &user) if err != nil { return nil, nil, err } @@ -158,7 +156,7 @@ func (s *GroupService) Add(groupname string, username string) (*Group, *Response // Caller must close resp.Body func (s *GroupService) RemoveWithContext(ctx context.Context, groupname string, username string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s&username=%s", groupname, username) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return nil, err } diff --git a/onpremise/issue.go b/onpremise/issue.go index 4f82017..e031ae4 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -27,9 +27,7 @@ const ( // IssueService handles Issues for the Jira instance / API. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue -type IssueService struct { - client *Client -} +type IssueService service // UpdateQueryOptions specifies the optional parameters to the Edit issue type UpdateQueryOptions struct { @@ -617,7 +615,7 @@ type RemoteLinkStatus struct { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getIssue func (s *IssueService) GetWithContext(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -651,7 +649,7 @@ func (s *IssueService) Get(issueID string, options *GetQueryOptions) (*Issue, *R // Caller must close resp.Body. func (s *IssueService) DownloadAttachmentWithContext(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("secure/attachment/%s/", attachmentID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, err } @@ -719,7 +717,7 @@ func (s *IssueService) PostAttachment(issueID string, r io.Reader, attachmentNam func (s *IssueService) DeleteAttachmentWithContext(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/attachment/%s", attachmentID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return nil, err } @@ -744,7 +742,7 @@ func (s *IssueService) DeleteAttachment(attachmentID string) (*Response, error) func (s *IssueService) DeleteLinkWithContext(ctx context.Context, linkID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLink/%s", linkID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return nil, err } @@ -771,7 +769,7 @@ func (s *IssueService) DeleteLink(linkID string) (*Response, error) { func (s *IssueService) GetWorklogsWithContext(ctx context.Context, issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -816,7 +814,7 @@ func WithQueryOptions(options interface{}) func(*http.Request) error { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-createIssues func (s *IssueService) CreateWithContext(ctx context.Context, issue *Issue) (*Issue, *Response, error) { apiEndpoint := "rest/api/2/issue" - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, issue) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, issue) if err != nil { return nil, nil, err } @@ -855,7 +853,7 @@ func (s *IssueService) UpdateWithOptionsWithContext(ctx context.Context, issue * if err != nil { return nil, nil, err } - req, err := s.client.NewRequestWithContext(ctx, "PUT", url, issue) + req, err := s.client.NewRequest(ctx, "PUT", url, issue) if err != nil { return nil, nil, err } @@ -895,7 +893,7 @@ func (s *IssueService) Update(issue *Issue) (*Issue, *Response, error) { // Caller must close resp.Body func (s *IssueService) UpdateIssueWithContext(ctx context.Context, jiraID string, data map[string]interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", jiraID) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, data) + req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, data) if err != nil { return nil, err } @@ -920,7 +918,7 @@ func (s *IssueService) UpdateIssue(jiraID string, data map[string]interface{}) ( // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-addComment func (s *IssueService) AddCommentWithContext(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment", issueID) - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, comment) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, comment) if err != nil { return nil, nil, err } @@ -950,7 +948,7 @@ func (s *IssueService) UpdateCommentWithContext(ctx context.Context, issueID str Body: comment.Body, } apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, comment.ID) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, reqBody) + req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, reqBody) if err != nil { return nil, nil, err } @@ -974,7 +972,7 @@ func (s *IssueService) UpdateComment(issueID string, comment *Comment) (*Comment // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-issue-issueIdOrKey-comment-id-delete func (s *IssueService) DeleteCommentWithContext(ctx context.Context, issueID, commentID string) error { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, commentID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return err } @@ -999,7 +997,7 @@ func (s *IssueService) DeleteComment(issueID, commentID string) error { // https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-worklog-post func (s *IssueService) AddWorklogRecordWithContext(ctx context.Context, issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, record) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, record) if err != nil { return nil, nil, err } @@ -1031,7 +1029,7 @@ func (s *IssueService) AddWorklogRecord(issueID string, record *WorklogRecord, o // https://docs.atlassian.com/software/jira/docs/api/REST/7.1.2/#api/2/issue-updateWorklog func (s *IssueService) UpdateWorklogRecordWithContext(ctx context.Context, issueID, worklogID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog/%s", issueID, worklogID) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, record) + req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, record) if err != nil { return nil, nil, err } @@ -1064,7 +1062,7 @@ func (s *IssueService) UpdateWorklogRecord(issueID, worklogID string, record *Wo // Caller must close resp.Body func (s *IssueService) AddLinkWithContext(ctx context.Context, issueLink *IssueLink) (*Response, error) { apiEndpoint := "rest/api/2/issueLink" - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, issueLink) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, issueLink) if err != nil { return nil, err } @@ -1115,7 +1113,7 @@ func (s *IssueService) SearchWithContext(ctx context.Context, jql string, option u.RawQuery = uv.Encode() - req, err := s.client.NewRequestWithContext(ctx, "GET", u.String(), nil) + req, err := s.client.NewRequest(ctx, "GET", u.String(), nil) if err != nil { return []Issue{}, nil, err } @@ -1185,7 +1183,7 @@ func (s *IssueService) SearchPages(jql string, options *SearchOptions, f func(Is // GetCustomFieldsWithContext returns a map of customfield_* keys with string values func (s *IssueService) GetCustomFieldsWithContext(ctx context.Context, issueID string) (CustomFields, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -1230,7 +1228,7 @@ func (s *IssueService) GetCustomFields(issueID string) (CustomFields, *Response, // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getTransitions func (s *IssueService) GetTransitionsWithContext(ctx context.Context, id string) ([]Transition, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions?expand=transitions.fields", id) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -1274,7 +1272,7 @@ func (s *IssueService) DoTransition(ticketID, transitionID string) (*Response, e func (s *IssueService) DoTransitionWithPayloadWithContext(ctx context.Context, ticketID, payload interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions", ticketID) - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, payload) if err != nil { return nil, err } @@ -1384,7 +1382,7 @@ func (s *IssueService) DeleteWithContext(ctx context.Context, issueID string) (* deletePayload["deleteSubtasks"] = "true" content, _ := json.Marshal(deletePayload) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, content) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, content) if err != nil { return nil, err } @@ -1405,7 +1403,7 @@ func (s *IssueService) Delete(issueID string) (*Response, error) { func (s *IssueService) GetWatchersWithContext(ctx context.Context, issueID string) (*[]User, *Response, error) { watchesAPIEndpoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) - req, err := s.client.NewRequestWithContext(ctx, "GET", watchesAPIEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", watchesAPIEndpoint, nil) if err != nil { return nil, nil, err } @@ -1443,7 +1441,7 @@ func (s *IssueService) GetWatchers(issueID string) (*[]User, *Response, error) { func (s *IssueService) AddWatcherWithContext(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, userName) + req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, userName) if err != nil { return nil, err } @@ -1469,7 +1467,7 @@ func (s *IssueService) AddWatcher(issueID string, userName string) (*Response, e func (s *IssueService) RemoveWatcherWithContext(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, userName) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, userName) if err != nil { return nil, err } @@ -1495,7 +1493,7 @@ func (s *IssueService) RemoveWatcher(issueID string, userName string) (*Response func (s *IssueService) UpdateAssigneeWithContext(ctx context.Context, issueID string, assignee *User) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/assignee", issueID) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndPoint, assignee) + req, err := s.client.NewRequest(ctx, "PUT", apiEndPoint, assignee) if err != nil { return nil, err } @@ -1529,7 +1527,7 @@ func (c ChangelogHistory) CreatedTime() (time.Time, error) { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getRemoteIssueLinks func (s *IssueService) GetRemoteLinksWithContext(ctx context.Context, id string) (*[]RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", id) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -1553,7 +1551,7 @@ func (s *IssueService) GetRemoteLinks(id string) (*[]RemoteLink, *Response, erro // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-remotelink-post func (s *IssueService) AddRemoteLinkWithContext(ctx context.Context, issueID string, remotelink *RemoteLink) (*RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", issueID) - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, remotelink) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, remotelink) if err != nil { return nil, nil, err } @@ -1578,7 +1576,7 @@ func (s *IssueService) AddRemoteLink(issueID string, remotelink *RemoteLink) (*R // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-remote-links/#api-rest-api-2-issue-issueidorkey-remotelink-linkid-put func (s *IssueService) UpdateRemoteLinkWithContext(ctx context.Context, issueID string, linkID int, remotelink *RemoteLink) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink/%d", issueID, linkID) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, remotelink) + req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, remotelink) if err != nil { return nil, err } diff --git a/onpremise/issuelinktype.go b/onpremise/issuelinktype.go index c8cf542..c6af34d 100644 --- a/onpremise/issuelinktype.go +++ b/onpremise/issuelinktype.go @@ -10,16 +10,14 @@ import ( // IssueLinkTypeService handles issue link types for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Issue-link-types -type IssueLinkTypeService struct { - client *Client -} +type IssueLinkTypeService service // GetListWithContext gets all of the issue link types from Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-get func (s *IssueLinkTypeService) GetListWithContext(ctx context.Context) ([]IssueLinkType, *Response, error) { apiEndpoint := "rest/api/2/issueLinkType" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -42,7 +40,7 @@ func (s *IssueLinkTypeService) GetList() ([]IssueLinkType, *Response, error) { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-get func (s *IssueLinkTypeService) GetWithContext(ctx context.Context, ID string) (*IssueLinkType, *Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) if err != nil { return nil, nil, err } @@ -65,7 +63,7 @@ func (s *IssueLinkTypeService) Get(ID string) (*IssueLinkType, *Response, error) // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-post func (s *IssueLinkTypeService) CreateWithContext(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := "/rest/api/2/issueLinkType" - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, linkType) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, linkType) if err != nil { return nil, nil, err } @@ -101,7 +99,7 @@ func (s *IssueLinkTypeService) Create(linkType *IssueLinkType) (*IssueLinkType, // Caller must close resp.Body func (s *IssueLinkTypeService) UpdateWithContext(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", linkType.ID) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, linkType) + req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, linkType) if err != nil { return nil, nil, err } @@ -125,7 +123,7 @@ func (s *IssueLinkTypeService) Update(linkType *IssueLinkType) (*IssueLinkType, // Caller must close resp.Body func (s *IssueLinkTypeService) DeleteWithContext(ctx context.Context, ID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return nil, err } diff --git a/onpremise/jira.go b/onpremise/jira.go index 456708d..dea2181 100644 --- a/onpremise/jira.go +++ b/onpremise/jira.go @@ -10,27 +10,37 @@ import ( "net/url" "reflect" "strings" + "sync" "github.com/google/go-querystring/query" ) -// httpClient defines an interface for an http.Client implementation so that alternative -// http Clients can be passed in for making requests -type httpClient interface { - Do(request *http.Request) (response *http.Response, err error) -} +const ( + ClientVersion = "2.0.0" + + defaultUserAgent = "go-jira" + "/" + ClientVersion +) // A Client manages communication with the Jira API. type Client struct { - // HTTP client used to communicate with the API. - client httpClient + clientMu sync.Mutex // clientMu protects the client during calls that modify it. + client *http.Client // HTTP client used to communicate with the API. // Base URL for API requests. - baseURL *url.URL + // Should be set to a domain endpoint of the Jira instance. + // BaseURL should always be specified with a trailing slash. + BaseURL *url.URL + + // User agent used when communicating with the Jira API. + UserAgent string // Session storage if the user authenticates with a Session cookie + // TODO Needed in Cloud and/or onpremise? session *Session + // Reuse a single struct instead of allocating one for each service on the heap. + common service + // Services used for talking to different parts of the Jira API. Authentication *AuthenticationService Issue *IssueService @@ -56,62 +66,77 @@ type Client struct { Request *RequestService } -// NewClient returns a new Jira API client. -// If a nil httpClient is provided, http.DefaultClient will be used. -// To use API methods which require authentication you can follow the preferred solution and -// provide an http.Client that will perform the authentication for you with OAuth and HTTP Basic (such as that provided by the golang.org/x/oauth2 library). -// As an alternative you can use Session Cookie based authentication provided by this package as well. -// See https://docs.atlassian.com/jira/REST/latest/#authentication +// service is the base structure to bundle API services +// under a sub-struct. +type service struct { + client *Client +} + +// Client returns the http.Client used by this Jira client. +func (c *Client) Client() *http.Client { + c.clientMu.Lock() + defer c.clientMu.Unlock() + clientCopy := *c.client + return &clientCopy +} + +// NewClient returns a new Jira API client with provided base URL (often is your Jira hostname) +// If a nil httpClient is provided, a new http.Client will be used. +// To use API methods which require authentication, provide an http.Client that will perform the authentication for you (such as that provided by the golang.org/x/oauth2 library). // baseURL is the HTTP endpoint of your Jira instance and should always be specified with a trailing slash. -func NewClient(httpClient httpClient, baseURL string) (*Client, error) { +func NewClient(baseURL string, httpClient *http.Client) (*Client, error) { if httpClient == nil { - httpClient = http.DefaultClient - } - - // ensure the baseURL contains a trailing slash so that all paths are preserved in later calls - if !strings.HasSuffix(baseURL, "/") { - baseURL += "/" + httpClient = &http.Client{} } - parsedBaseURL, err := url.Parse(baseURL) + baseEndpoint, err := url.Parse(baseURL) if err != nil { return nil, err } + // ensure the baseURL contains a trailing slash so that all paths are preserved in later calls + if !strings.HasSuffix(baseEndpoint.Path, "/") { + baseEndpoint.Path += "/" + } c := &Client{ - client: httpClient, - baseURL: parsedBaseURL, + client: httpClient, + BaseURL: baseEndpoint, + UserAgent: defaultUserAgent, } + c.common.client = c + + // TODO Check if the authentication service is still needed (because of the transports) c.Authentication = &AuthenticationService{client: c} - c.Issue = &IssueService{client: c} - c.Project = &ProjectService{client: c} - c.Board = &BoardService{client: c} - c.Sprint = &SprintService{client: c} - c.User = &UserService{client: c} - c.Group = &GroupService{client: c} - c.Version = &VersionService{client: c} - c.Priority = &PriorityService{client: c} - c.Field = &FieldService{client: c} - c.Component = &ComponentService{client: c} - c.Resolution = &ResolutionService{client: c} - c.StatusCategory = &StatusCategoryService{client: c} - c.Filter = &FilterService{client: c} - c.Role = &RoleService{client: c} - c.PermissionScheme = &PermissionSchemeService{client: c} - c.Status = &StatusService{client: c} - c.IssueLinkType = &IssueLinkTypeService{client: c} - c.Organization = &OrganizationService{client: c} - c.ServiceDesk = &ServiceDeskService{client: c} - c.Customer = &CustomerService{client: c} - c.Request = &RequestService{client: c} + c.Issue = (*IssueService)(&c.common) + c.Project = (*ProjectService)(&c.common) + c.Board = (*BoardService)(&c.common) + c.Sprint = (*SprintService)(&c.common) + c.User = (*UserService)(&c.common) + c.Group = (*GroupService)(&c.common) + c.Version = (*VersionService)(&c.common) + c.Priority = (*PriorityService)(&c.common) + c.Field = (*FieldService)(&c.common) + c.Component = (*ComponentService)(&c.common) + c.Resolution = (*ResolutionService)(&c.common) + c.StatusCategory = (*StatusCategoryService)(&c.common) + c.Filter = (*FilterService)(&c.common) + c.Role = (*RoleService)(&c.common) + c.PermissionScheme = (*PermissionSchemeService)(&c.common) + c.Status = (*StatusService)(&c.common) + c.IssueLinkType = (*IssueLinkTypeService)(&c.common) + c.Organization = (*OrganizationService)(&c.common) + c.ServiceDesk = (*ServiceDeskService)(&c.common) + c.Customer = (*CustomerService)(&c.common) + c.Request = (*RequestService)(&c.common) return c, nil } -// NewRawRequestWithContext creates an API request. +// TODO Do we need it? +// NewRawRequest creates an API request. // A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client. // Allows using an optional native io.Reader for sourcing the request body. -func (c *Client) NewRawRequestWithContext(ctx context.Context, method, urlStr string, body io.Reader) (*http.Request, error) { +func (c *Client) NewRawRequest(ctx context.Context, method, urlStr string, body io.Reader) (*http.Request, error) { rel, err := url.Parse(urlStr) if err != nil { return nil, err @@ -119,7 +144,7 @@ func (c *Client) NewRawRequestWithContext(ctx context.Context, method, urlStr st // Relative URLs should be specified without a preceding slash since baseURL will have the trailing slash rel.Path = strings.TrimLeft(rel.Path, "/") - u := c.baseURL.ResolveReference(rel) + u := c.BaseURL.ResolveReference(rel) req, err := http.NewRequestWithContext(ctx, method, u.String(), body) if err != nil { @@ -146,24 +171,21 @@ func (c *Client) NewRawRequestWithContext(ctx context.Context, method, urlStr st return req, nil } -// NewRawRequest wraps NewRawRequestWithContext using the background context. -func (c *Client) NewRawRequest(method, urlStr string, body io.Reader) (*http.Request, error) { - return c.NewRawRequestWithContext(context.Background(), method, urlStr, body) -} - -// NewRequestWithContext creates an API request. -// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client. +// NewRequest creates an API request. +// A relative URL can be provided in urlStr, in which case it is resolved relative to the BaseURL of the Client. // If specified, the value pointed to by body is JSON encoded and included as the request body. -func (c *Client) NewRequestWithContext(ctx context.Context, method, urlStr string, body interface{}) (*http.Request, error) { +func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body interface{}) (*http.Request, error) { rel, err := url.Parse(urlStr) if err != nil { return nil, err } - // Relative URLs should be specified without a preceding slash since baseURL will have the trailing slash + // Relative URLs should be specified without a preceding slash since BaseURL will have the trailing slash rel.Path = strings.TrimLeft(rel.Path, "/") - u := c.baseURL.ResolveReference(rel) + u := c.BaseURL.ResolveReference(rel) + // TODO This part is the difference between NewRawRequestWithContext + // Check if we can get this working in one function var buf io.ReadWriter if body != nil { buf = new(bytes.Buffer) @@ -198,15 +220,10 @@ func (c *Client) NewRequestWithContext(ctx context.Context, method, urlStr strin return req, nil } -// NewRequest wraps NewRequestWithContext using the background context. -func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) { - return c.NewRequestWithContext(context.Background(), method, urlStr, body) -} - -// addOptions adds the parameters in opt as URL query parameters to s. opt +// addOptions adds the parameters in opts as URL query parameters to s. opts // must be a struct whose fields may contain "url" tags. -func addOptions(s string, opt interface{}) (string, error) { - v := reflect.ValueOf(opt) +func addOptions(s string, opts interface{}) (string, error) { + v := reflect.ValueOf(opts) if v.Kind() == reflect.Ptr && v.IsNil() { return s, nil } @@ -216,7 +233,7 @@ func addOptions(s string, opt interface{}) (string, error) { return s, err } - qs, err := query.Values(opt) + qs, err := query.Values(opts) if err != nil { return s, err } @@ -236,7 +253,7 @@ func (c *Client) NewMultiPartRequestWithContext(ctx context.Context, method, url // Relative URLs should be specified without a preceding slash since baseURL will have the trailing slash rel.Path = strings.TrimLeft(rel.Path, "/") - u := c.baseURL.ResolveReference(rel) + u := c.BaseURL.ResolveReference(rel) req, err := http.NewRequestWithContext(ctx, method, u.String(), buf) if err != nil { @@ -307,12 +324,6 @@ func CheckResponse(r *http.Response) error { return err } -// GetBaseURL will return you the Base URL. -// This is the same URL as in the NewClient constructor -func (c *Client) GetBaseURL() url.URL { - return *c.baseURL -} - // Response represents Jira API response. It wraps http.Response returned from // API and provides information about paging. type Response struct { diff --git a/onpremise/jira_test.go b/onpremise/jira_test.go index f9a5f6e..1a0b9e1 100644 --- a/onpremise/jira_test.go +++ b/onpremise/jira_test.go @@ -2,6 +2,7 @@ package onpremise import ( "bytes" + "context" "fmt" "io" "net/http" @@ -36,7 +37,7 @@ func setup() { testServer = httptest.NewServer(testMux) // jira client configured to use test server - testClient, _ = NewClient(nil, testServer.URL) + testClient, _ = NewClient(testServer.URL, nil) } // teardown closes the test HTTP server. @@ -73,7 +74,7 @@ func testRequestParams(t *testing.T, r *http.Request, want map[string]string) { } func TestNewClient_WrongUrl(t *testing.T) { - c, err := NewClient(nil, "://issues.apache.org/jira/") + c, err := NewClient("://issues.apache.org/jira/", nil) if err == nil { t.Error("Expected an error. Got none") @@ -87,7 +88,7 @@ func TestNewClient_WithHttpClient(t *testing.T) { httpClient := http.DefaultClient httpClient.Timeout = 10 * time.Minute - c, err := NewClient(httpClient, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, httpClient) if err != nil { t.Errorf("Got an error: %s", err) } @@ -101,7 +102,7 @@ func TestNewClient_WithHttpClient(t *testing.T) { } func TestNewClient_WithServices(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("Got an error: %s", err) @@ -157,14 +158,14 @@ func TestCheckResponse(t *testing.T) { } func TestClient_NewRequest(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } inURL, outURL := "rest/api/2/issue/", testJiraInstanceURL+"rest/api/2/issue/" inBody, outBody := &Issue{Key: "MESOS"}, `{"key":"MESOS"}`+"\n" - req, _ := c.NewRequest("GET", inURL, inBody) + req, _ := c.NewRequest(context.Background(), "GET", inURL, inBody) // Test that relative URL was expanded if got, want := req.URL.String(), outURL; got != want { @@ -179,7 +180,7 @@ func TestClient_NewRequest(t *testing.T) { } func TestClient_NewRawRequest(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } @@ -188,7 +189,7 @@ func TestClient_NewRawRequest(t *testing.T) { outBody := `{"key":"MESOS"}` + "\n" inBody := outBody - req, _ := c.NewRawRequest("GET", inURL, strings.NewReader(outBody)) + req, _ := c.NewRawRequest(context.Background(), "GET", inURL, strings.NewReader(outBody)) // Test that relative URL was expanded if got, want := req.URL.String(), outURL; got != want { @@ -212,16 +213,16 @@ func testURLParseError(t *testing.T, err error) { } func TestClient_NewRequest_BadURL(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } - _, err = c.NewRequest("GET", ":", nil) + _, err = c.NewRequest(context.Background(), "GET", ":", nil) testURLParseError(t, err) } func TestClient_NewRequest_SessionCookies(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } @@ -232,7 +233,7 @@ func TestClient_NewRequest_SessionCookies(t *testing.T) { inURL := "rest/api/2/issue/" inBody := &Issue{Key: "MESOS"} - req, err := c.NewRequest("GET", inURL, inBody) + req, err := c.NewRequest(context.Background(), "GET", inURL, inBody) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) @@ -250,7 +251,7 @@ func TestClient_NewRequest_SessionCookies(t *testing.T) { } func TestClient_NewRequest_BasicAuth(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } @@ -259,7 +260,7 @@ func TestClient_NewRequest_BasicAuth(t *testing.T) { inURL := "rest/api/2/issue/" inBody := &Issue{Key: "MESOS"} - req, err := c.NewRequest("GET", inURL, inBody) + req, err := c.NewRequest(context.Background(), "GET", inURL, inBody) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) @@ -276,11 +277,11 @@ func TestClient_NewRequest_BasicAuth(t *testing.T) { // since there is no difference between an HTTP request body that is an empty string versus one that is not set at all. // However in certain cases, intermediate systems may treat these differently resulting in subtle errors. func TestClient_NewRequest_EmptyBody(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } - req, err := c.NewRequest("GET", "/", nil) + req, err := c.NewRequest(context.Background(), "GET", "/", nil) if err != nil { t.Fatalf("NewRequest returned unexpected error: %v", err) } @@ -290,7 +291,7 @@ func TestClient_NewRequest_EmptyBody(t *testing.T) { } func TestClient_NewMultiPartRequest(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } @@ -323,7 +324,7 @@ func TestClient_NewMultiPartRequest(t *testing.T) { } func TestClient_NewMultiPartRequest_BasicAuth(t *testing.T) { - c, err := NewClient(nil, testJiraInstanceURL) + c, err := NewClient(testJiraInstanceURL, nil) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } @@ -363,7 +364,7 @@ func TestClient_Do(t *testing.T) { fmt.Fprint(w, `{"A":"a"}`) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) body := new(foo) testClient.Do(req, body) @@ -384,7 +385,7 @@ func TestClient_Do_HTTPResponse(t *testing.T) { fmt.Fprint(w, `{"A":"a"}`) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) res, _ := testClient.Do(req, nil) _, err := io.ReadAll(res.Body) @@ -403,7 +404,7 @@ func TestClient_Do_HTTPError(t *testing.T) { http.Error(w, "Bad Request", 400) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) _, err := testClient.Do(req, nil) if err == nil { @@ -421,7 +422,7 @@ func TestClient_Do_RedirectLoop(t *testing.T) { http.Redirect(w, r, "/", http.StatusFound) }) - req, _ := testClient.NewRequest("GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) _, err := testClient.Do(req, nil) if err == nil { @@ -431,22 +432,3 @@ func TestClient_Do_RedirectLoop(t *testing.T) { t.Errorf("Expected a URL error; got %+v.", err) } } - -func TestClient_GetBaseURL_WithURL(t *testing.T) { - u, err := url.Parse(testJiraInstanceURL) - if err != nil { - t.Errorf("URL parsing -> Got an error: %s", err) - } - - c, err := NewClient(nil, testJiraInstanceURL) - if err != nil { - t.Errorf("Client creation -> Got an error: %s", err) - } - if c == nil { - t.Error("Expected a client. Got none") - } - - if b := c.GetBaseURL(); !reflect.DeepEqual(b, *u) { - t.Errorf("Base URLs are not equal. Expected %+v, got %+v", *u, b) - } -} diff --git a/onpremise/metaissue.go b/onpremise/metaissue.go index 6fff176..dd35a7f 100644 --- a/onpremise/metaissue.go +++ b/onpremise/metaissue.go @@ -62,7 +62,7 @@ func (s *IssueService) GetCreateMeta(projectkeys string) (*CreateMetaInfo, *Resp func (s *IssueService) GetCreateMetaWithOptionsWithContext(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { apiEndpoint := "rest/api/2/issue/createmeta" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -93,7 +93,7 @@ func (s *IssueService) GetCreateMetaWithOptions(options *GetQueryOptions) (*Crea func (s *IssueService) GetEditMetaWithContext(ctx context.Context, issue *Issue) (*EditMetaInfo, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/issue/%s/editmeta", issue.Key) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/organization.go b/onpremise/organization.go index 373479b..7a752d5 100644 --- a/onpremise/organization.go +++ b/onpremise/organization.go @@ -8,9 +8,7 @@ import ( // OrganizationService handles Organizations for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/ -type OrganizationService struct { - client *Client -} +type OrganizationService service // OrganizationCreationDTO is DTO for creat organization API type OrganizationCreationDTO struct { @@ -68,7 +66,7 @@ func (s *OrganizationService) GetAllOrganizationsWithContext(ctx context.Context apiEndPoint += fmt.Sprintf("&accountId=%s", accountID) } - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -101,7 +99,7 @@ func (s *OrganizationService) CreateOrganizationWithContext(ctx context.Context, Name: name, } - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, organization) + req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, organization) req.Header.Set("Accept", "application/json") if err != nil { @@ -133,7 +131,7 @@ func (s *OrganizationService) CreateOrganization(name string) (*Organization, *R func (s *OrganizationService) GetOrganizationWithContext(ctx context.Context, organizationID int) (*Organization, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -165,7 +163,7 @@ func (s *OrganizationService) GetOrganization(organizationID int) (*Organization func (s *OrganizationService) DeleteOrganizationWithContext(ctx context.Context, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) if err != nil { return nil, err @@ -195,7 +193,7 @@ func (s *OrganizationService) DeleteOrganization(organizationID int) (*Response, func (s *OrganizationService) GetPropertiesKeysWithContext(ctx context.Context, organizationID int) (*PropertyKeys, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property", organizationID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -225,7 +223,7 @@ func (s *OrganizationService) GetPropertiesKeys(organizationID int) (*PropertyKe func (s *OrganizationService) GetPropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*EntityProperty, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -256,7 +254,7 @@ func (s *OrganizationService) GetProperty(organizationID int, propertyKey string func (s *OrganizationService) SetPropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "PUT", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -285,7 +283,7 @@ func (s *OrganizationService) SetProperty(organizationID int, propertyKey string func (s *OrganizationService) DeletePropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -317,7 +315,7 @@ func (s *OrganizationService) DeleteProperty(organizationID int, propertyKey str func (s *OrganizationService) GetUsersWithContext(ctx context.Context, organizationID int, start int, limit int) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user?start=%d&limit=%d", organizationID, start, limit) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -346,7 +344,7 @@ func (s *OrganizationService) GetUsers(organizationID int, start int, limit int) func (s *OrganizationService) AddUsersWithContext(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, users) + req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, users) if err != nil { return nil, err @@ -374,7 +372,7 @@ func (s *OrganizationService) AddUsers(organizationID int, users OrganizationUse func (s *OrganizationService) RemoveUsersWithContext(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { diff --git a/onpremise/permissionscheme.go b/onpremise/permissionscheme.go index f81794e..3257eed 100644 --- a/onpremise/permissionscheme.go +++ b/onpremise/permissionscheme.go @@ -8,9 +8,8 @@ import ( // PermissionSchemeService handles permissionschemes for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Permissionscheme -type PermissionSchemeService struct { - client *Client -} +type PermissionSchemeService service + type PermissionSchemes struct { PermissionSchemes []PermissionScheme `json:"permissionSchemes" structs:"permissionSchemes"` } @@ -33,7 +32,7 @@ type Holder struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-get func (s *PermissionSchemeService) GetListWithContext(ctx context.Context) (*PermissionSchemes, *Response, error) { apiEndpoint := "/rest/api/3/permissionscheme" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -58,7 +57,7 @@ func (s *PermissionSchemeService) GetList() (*PermissionSchemes, *Response, erro // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-schemeId-get func (s *PermissionSchemeService) GetWithContext(ctx context.Context, schemeID int) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/3/permissionscheme/%d", schemeID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/priority.go b/onpremise/priority.go index 1d3e46b..9ec0dbe 100644 --- a/onpremise/priority.go +++ b/onpremise/priority.go @@ -5,9 +5,7 @@ import "context" // PriorityService handles priorities for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Priority -type PriorityService struct { - client *Client -} +type PriorityService service // Priority represents a priority of a Jira issue. // Typical types are "Normal", "Moderate", "Urgent", ... @@ -25,7 +23,7 @@ type Priority struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-priority-get func (s *PriorityService) GetListWithContext(ctx context.Context) ([]Priority, *Response, error) { apiEndpoint := "rest/api/2/priority" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/project.go b/onpremise/project.go index 47782f4..6b6e8d7 100644 --- a/onpremise/project.go +++ b/onpremise/project.go @@ -10,9 +10,7 @@ import ( // ProjectService handles projects for the Jira instance / API. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project -type ProjectService struct { - client *Client -} +type ProjectService service // ProjectList represent a list of Projects type ProjectList []struct { @@ -99,7 +97,7 @@ func (s *ProjectService) GetList() (*ProjectList, *Response, error) { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects func (s *ProjectService) ListWithOptionsWithContext(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { apiEndpoint := "rest/api/2/project" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -134,7 +132,7 @@ func (s *ProjectService) ListWithOptions(options *GetQueryOptions) (*ProjectList // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject func (s *ProjectService) GetWithContext(ctx context.Context, projectID string) (*Project, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/project/%s", projectID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -161,7 +159,7 @@ func (s *ProjectService) Get(projectID string) (*Project, *Response, error) { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject func (s *ProjectService) GetPermissionSchemeWithContext(ctx context.Context, projectID string) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s/permissionscheme", projectID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/request.go b/onpremise/request.go index 3336ce7..b096005 100644 --- a/onpremise/request.go +++ b/onpremise/request.go @@ -6,9 +6,7 @@ import ( ) // RequestService handles ServiceDesk customer requests for the Jira instance / API. -type RequestService struct { - client *Client -} +type RequestService service // Request represents a ServiceDesk customer request. type Request struct { @@ -78,7 +76,7 @@ func (r *RequestService) CreateWithContext(ctx context.Context, requester string payload.FieldValues[field.FieldID] = field.Value } - req, err := r.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload) + req, err := r.client.NewRequest(ctx, "POST", apiEndpoint, payload) if err != nil { return nil, nil, err } @@ -103,7 +101,7 @@ func (r *RequestService) Create(requester string, participants []string, request func (r *RequestService) CreateCommentWithContext(ctx context.Context, issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/request/%v/comment", issueIDOrKey) - req, err := r.client.NewRequestWithContext(ctx, "POST", apiEndpoint, comment) + req, err := r.client.NewRequest(ctx, "POST", apiEndpoint, comment) if err != nil { return nil, nil, err } diff --git a/onpremise/resolution.go b/onpremise/resolution.go index 76db3d6..c2613fb 100644 --- a/onpremise/resolution.go +++ b/onpremise/resolution.go @@ -5,9 +5,7 @@ import "context" // ResolutionService handles resolutions for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Resolution -type ResolutionService struct { - client *Client -} +type ResolutionService service // Resolution represents a resolution of a Jira issue. // Typical types are "Fixed", "Suspended", "Won't Fix", ... @@ -23,7 +21,7 @@ type Resolution struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-resolution-get func (s *ResolutionService) GetListWithContext(ctx context.Context) ([]Resolution, *Response, error) { apiEndpoint := "rest/api/2/resolution" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/role.go b/onpremise/role.go index 01d94bf..f3b9f72 100644 --- a/onpremise/role.go +++ b/onpremise/role.go @@ -8,9 +8,7 @@ import ( // RoleService handles roles for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Role -type RoleService struct { - client *Client -} +type RoleService service // Role represents a Jira product role type Role struct { @@ -41,7 +39,7 @@ type ActorUser struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-get func (s *RoleService) GetListWithContext(ctx context.Context) (*[]Role, *Response, error) { apiEndpoint := "rest/api/3/role" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -64,7 +62,7 @@ func (s *RoleService) GetList() (*[]Role, *Response, error) { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-id-get func (s *RoleService) GetWithContext(ctx context.Context, roleID int) (*Role, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/3/role/%d", roleID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/servicedesk.go b/onpremise/servicedesk.go index d935cc1..c303904 100644 --- a/onpremise/servicedesk.go +++ b/onpremise/servicedesk.go @@ -10,9 +10,7 @@ import ( ) // ServiceDeskService handles ServiceDesk for the Jira instance / API. -type ServiceDeskService struct { - client *Client -} +type ServiceDeskService service // ServiceDeskOrganizationDTO is a DTO for ServiceDesk organizations type ServiceDeskOrganizationDTO struct { @@ -29,7 +27,7 @@ func (s *ServiceDeskService) GetOrganizationsWithContext(ctx context.Context, se apiEndPoint += fmt.Sprintf("&accountId=%s", accountID) } - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -65,7 +63,7 @@ func (s *ServiceDeskService) AddOrganizationWithContext(ctx context.Context, ser OrganizationID: organizationID, } - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, organization) + req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, organization) if err != nil { return nil, err @@ -100,7 +98,7 @@ func (s *ServiceDeskService) RemoveOrganizationWithContext(ctx context.Context, OrganizationID: organizationID, } - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, organization) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, organization) if err != nil { return nil, err @@ -132,7 +130,7 @@ func (s *ServiceDeskService) AddCustomersWithContext(ctx context.Context, servic }{ AccountIDs: acountIDs, } - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, payload) if err != nil { return nil, err } @@ -164,7 +162,7 @@ func (s *ServiceDeskService) RemoveCustomersWithContext(ctx context.Context, ser }{ AccountIDs: acountIDs, } - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, payload) if err != nil { return nil, err } @@ -190,7 +188,7 @@ func (s *ServiceDeskService) RemoveCustomers(serviceDeskID interface{}, acountID // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-get func (s *ServiceDeskService) ListCustomersWithContext(ctx context.Context, serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/sprint.go b/onpremise/sprint.go index d7560ca..667df05 100644 --- a/onpremise/sprint.go +++ b/onpremise/sprint.go @@ -9,9 +9,7 @@ import ( // SprintService handles sprints in Jira Agile API. // See https://docs.atlassian.com/jira-software/REST/cloud/ -type SprintService struct { - client *Client -} +type SprintService service // IssuesWrapper represents a wrapper struct for moving issues to sprint type IssuesWrapper struct { @@ -34,7 +32,7 @@ func (s *SprintService) MoveIssuesToSprintWithContext(ctx context.Context, sprin payload := IssuesWrapper{Issues: issueIDs} - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, payload) if err != nil { return nil, err @@ -61,7 +59,7 @@ func (s *SprintService) MoveIssuesToSprint(sprintID int, issueIDs []string) (*Re func (s *SprintService) GetIssuesForSprintWithContext(ctx context.Context, sprintID int) ([]Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err @@ -94,7 +92,7 @@ func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, er func (s *SprintService) GetIssueWithContext(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/issue/%s", issueID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err diff --git a/onpremise/status.go b/onpremise/status.go index 93b4ca9..0ca307f 100644 --- a/onpremise/status.go +++ b/onpremise/status.go @@ -5,9 +5,7 @@ import "context" // StatusService handles staties for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Workflow-statuses -type StatusService struct { - client *Client -} +type StatusService service // Status represents the current status of a Jira issue. // Typical status are "Open", "In Progress", "Closed", ... @@ -26,7 +24,7 @@ type Status struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-status-get func (s *StatusService) GetAllStatusesWithContext(ctx context.Context) ([]Status, *Response, error) { apiEndpoint := "rest/api/2/status" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err diff --git a/onpremise/statuscategory.go b/onpremise/statuscategory.go index 8918fbe..2035d7a 100644 --- a/onpremise/statuscategory.go +++ b/onpremise/statuscategory.go @@ -5,9 +5,7 @@ import "context" // StatusCategoryService handles status categories for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Statuscategory -type StatusCategoryService struct { - client *Client -} +type StatusCategoryService service // StatusCategory represents the category a status belongs to. // Those categories can be user defined in every Jira instance. @@ -32,7 +30,7 @@ const ( // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get func (s *StatusCategoryService) GetListWithContext(ctx context.Context) ([]StatusCategory, *Response, error) { apiEndpoint := "rest/api/2/statuscategory" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/user.go b/onpremise/user.go index bec5009..1064c0e 100644 --- a/onpremise/user.go +++ b/onpremise/user.go @@ -10,9 +10,7 @@ import ( // UserService handles users for the Jira instance / API. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Users -type UserService struct { - client *Client -} +type UserService service // User represents a Jira user. type User struct { @@ -51,7 +49,7 @@ type userSearchF func(userSearch) userSearch // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-get func (s *UserService) GetWithContext(ctx context.Context, accountId string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -75,7 +73,7 @@ func (s *UserService) Get(accountId string) (*User, *Response, error) { // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser func (s *UserService) GetByAccountIDWithContext(ctx context.Context, accountID string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -98,7 +96,7 @@ func (s *UserService) GetByAccountID(accountID string) (*User, *Response, error) // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-createUser func (s *UserService) CreateWithContext(ctx context.Context, user *User) (*User, *Response, error) { apiEndpoint := "/rest/api/2/user" - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, user) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, user) if err != nil { return nil, nil, err } @@ -135,7 +133,7 @@ func (s *UserService) Create(user *User) (*User, *Response, error) { // Caller must close resp.Body func (s *UserService) DeleteWithContext(ctx context.Context, accountId string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) - req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { return nil, err } @@ -158,7 +156,7 @@ func (s *UserService) Delete(accountId string) (*Response, error) { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-groups-get func (s *UserService) GetGroupsWithContext(ctx context.Context, accountId string) (*[]UserGroup, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?accountId=%s", accountId) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -181,7 +179,7 @@ func (s *UserService) GetGroups(accountId string) (*[]UserGroup, *Response, erro // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-myself-get func (s *UserService) GetSelfWithContext(ctx context.Context) (*User, *Response, error) { const apiEndpoint = "rest/api/2/myself" - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -275,7 +273,7 @@ func (s *UserService) FindWithContext(ctx context.Context, property string, twea } apiEndpoint := fmt.Sprintf("/rest/api/2/user/search?%s", queryString[:len(queryString)-1]) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/version.go b/onpremise/version.go index f4a8266..ec4ce2c 100644 --- a/onpremise/version.go +++ b/onpremise/version.go @@ -10,9 +10,7 @@ import ( // VersionService handles Versions for the Jira instance / API. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/version -type VersionService struct { - client *Client -} +type VersionService service // Version represents a single release version of a project type Version struct { @@ -33,7 +31,7 @@ type Version struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-get func (s *VersionService) GetWithContext(ctx context.Context, versionID int) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/version/%v", versionID) - req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -56,7 +54,7 @@ func (s *VersionService) Get(versionID int) (*Version, *Response, error) { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-post func (s *VersionService) CreateWithContext(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := "/rest/api/2/version" - req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, version) + req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, version) if err != nil { return nil, nil, err } @@ -92,7 +90,7 @@ func (s *VersionService) Create(version *Version) (*Version, *Response, error) { // Caller must close resp.Body func (s *VersionService) UpdateWithContext(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/version/%v", version.ID) - req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, version) + req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, version) if err != nil { return nil, nil, err } From 01115beed821c98592bf561ac55bacda2c3b9040 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 18:22:11 +0200 Subject: [PATCH 013/189] cloud: `NewMultiPartRequestWithContext` removed, `NewMultiPartRequest` requires `context` Fix #506 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ cloud/issue.go | 2 +- cloud/jira.go | 9 ++------- cloud/jira_test.go | 4 ++-- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f90595..2429f69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,6 +110,31 @@ Like client.NewRequest(context.Background(), "GET", .....) ``` +#### `NewMultiPartRequestWithContext` removed, `NewMultiPartRequest` requires `context` + +The function `client.NewMultiPartRequestWithContext()` has been removed. +`client.NewMultiPartRequest()` accepts now a context as the first argument. +This is a drop in replacement. + +Before: + +```go +client.NewMultiPartRequestWithContext(context.Background(), "GET", .....) +``` + +After: + +```go +client.NewMultiPartRequest(context.Background(), "GET", .....) +``` + +For people who used `jira.NewMultiPartRequest()`: You need to pass a context as the first argument. +Like + +```go +client.NewMultiPartRequest(context.Background(), "GET", .....) +``` + ### Breaking changes * Jira On-Premise and Jira Cloud have now different clients, because the API differs diff --git a/cloud/issue.go b/cloud/issue.go index e18d0db..2ee215f 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -689,7 +689,7 @@ func (s *IssueService) PostAttachmentWithContext(ctx context.Context, issueID st } writer.Close() - req, err := s.client.NewMultiPartRequestWithContext(ctx, "POST", apiEndpoint, b) + req, err := s.client.NewMultiPartRequest(ctx, "POST", apiEndpoint, b) if err != nil { return nil, nil, err } diff --git a/cloud/jira.go b/cloud/jira.go index f4273b7..f473a93 100644 --- a/cloud/jira.go +++ b/cloud/jira.go @@ -242,10 +242,10 @@ func addOptions(s string, opts interface{}) (string, error) { return u.String(), nil } -// NewMultiPartRequestWithContext creates an API request including a multi-part file. +// NewMultiPartRequest creates an API request including a multi-part file. // A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client. // If specified, the value pointed to by buf is a multipart form. -func (c *Client) NewMultiPartRequestWithContext(ctx context.Context, method, urlStr string, buf *bytes.Buffer) (*http.Request, error) { +func (c *Client) NewMultiPartRequest(ctx context.Context, method, urlStr string, buf *bytes.Buffer) (*http.Request, error) { rel, err := url.Parse(urlStr) if err != nil { return nil, err @@ -281,11 +281,6 @@ func (c *Client) NewMultiPartRequestWithContext(ctx context.Context, method, url return req, nil } -// NewMultiPartRequest wraps NewMultiPartRequestWithContext using the background context. -func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (*http.Request, error) { - return c.NewMultiPartRequestWithContext(context.Background(), method, urlStr, buf) -} - // Do sends an API request and returns the API response. // The API response is JSON decoded and stored in the value pointed to by v, or returned as an error if an API error has occurred. func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { diff --git a/cloud/jira_test.go b/cloud/jira_test.go index 224af7b..7bf134e 100644 --- a/cloud/jira_test.go +++ b/cloud/jira_test.go @@ -302,7 +302,7 @@ func TestClient_NewMultiPartRequest(t *testing.T) { inURL := "rest/api/2/issue/" inBuf := bytes.NewBufferString("teststring") - req, err := c.NewMultiPartRequest("GET", inURL, inBuf) + req, err := c.NewMultiPartRequest(context.Background(), "GET", inURL, inBuf) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) @@ -333,7 +333,7 @@ func TestClient_NewMultiPartRequest_BasicAuth(t *testing.T) { inURL := "rest/api/2/issue/" inBuf := bytes.NewBufferString("teststring") - req, err := c.NewMultiPartRequest("GET", inURL, inBuf) + req, err := c.NewMultiPartRequest(context.Background(), "GET", inURL, inBuf) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) From bd9c2225951bb73f2d1749971e851c45738a32e9 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 18:24:21 +0200 Subject: [PATCH 014/189] onpremise: `NewMultiPartRequestWithContext` removed, `NewMultiPartRequest` requires `context` Fix #506 --- onpremise/issue.go | 2 +- onpremise/jira.go | 9 ++------- onpremise/jira_test.go | 4 ++-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/onpremise/issue.go b/onpremise/issue.go index e031ae4..eb08747 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -689,7 +689,7 @@ func (s *IssueService) PostAttachmentWithContext(ctx context.Context, issueID st } writer.Close() - req, err := s.client.NewMultiPartRequestWithContext(ctx, "POST", apiEndpoint, b) + req, err := s.client.NewMultiPartRequest(ctx, "POST", apiEndpoint, b) if err != nil { return nil, nil, err } diff --git a/onpremise/jira.go b/onpremise/jira.go index dea2181..b98c966 100644 --- a/onpremise/jira.go +++ b/onpremise/jira.go @@ -242,10 +242,10 @@ func addOptions(s string, opts interface{}) (string, error) { return u.String(), nil } -// NewMultiPartRequestWithContext creates an API request including a multi-part file. +// NewMultiPartRequest creates an API request including a multi-part file. // A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client. // If specified, the value pointed to by buf is a multipart form. -func (c *Client) NewMultiPartRequestWithContext(ctx context.Context, method, urlStr string, buf *bytes.Buffer) (*http.Request, error) { +func (c *Client) NewMultiPartRequest(ctx context.Context, method, urlStr string, buf *bytes.Buffer) (*http.Request, error) { rel, err := url.Parse(urlStr) if err != nil { return nil, err @@ -281,11 +281,6 @@ func (c *Client) NewMultiPartRequestWithContext(ctx context.Context, method, url return req, nil } -// NewMultiPartRequest wraps NewMultiPartRequestWithContext using the background context. -func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (*http.Request, error) { - return c.NewMultiPartRequestWithContext(context.Background(), method, urlStr, buf) -} - // Do sends an API request and returns the API response. // The API response is JSON decoded and stored in the value pointed to by v, or returned as an error if an API error has occurred. func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { diff --git a/onpremise/jira_test.go b/onpremise/jira_test.go index 1a0b9e1..e78fc70 100644 --- a/onpremise/jira_test.go +++ b/onpremise/jira_test.go @@ -302,7 +302,7 @@ func TestClient_NewMultiPartRequest(t *testing.T) { inURL := "rest/api/2/issue/" inBuf := bytes.NewBufferString("teststring") - req, err := c.NewMultiPartRequest("GET", inURL, inBuf) + req, err := c.NewMultiPartRequest(context.Background(), "GET", inURL, inBuf) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) @@ -333,7 +333,7 @@ func TestClient_NewMultiPartRequest_BasicAuth(t *testing.T) { inURL := "rest/api/2/issue/" inBuf := bytes.NewBufferString("teststring") - req, err := c.NewMultiPartRequest("GET", inURL, inBuf) + req, err := c.NewMultiPartRequest(context.Background(), "GET", inURL, inBuf) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) From a1dd14e57967c8606d96c418c6576d72bebe54c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Sep 2022 18:25:35 +0200 Subject: [PATCH 015/189] chore(deps): bump github.com/google/go-cmp from 0.5.8 to 0.5.9 (#505) Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.8 to 0.5.9. - [Release notes](https://github.com/google/go-cmp/releases) - [Commits](https://github.com/google/go-cmp/compare/v0.5.8...v0.5.9) --- updated-dependencies: - dependency-name: github.com/google/go-cmp dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e5ebce3..bf4eca0 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/fatih/structs v1.1.0 github.com/golang-jwt/jwt/v4 v4.4.2 - github.com/google/go-cmp v0.5.8 + github.com/google/go-cmp v0.5.9 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d diff --git a/go.sum b/go.sum index 7f282ab..6dcc5c0 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM= From 6bc4139ee422bf0c60dd00e22c3bce50de9c4da4 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 18:29:18 +0200 Subject: [PATCH 016/189] #484: Make contribution easier to this project (#494) * GitHub Actions: Split testing workflow into several jobs Related #484 * GitHub issues: Remove reference to github.com/go-jira/jira Initially we received a few bug tickets related to github.com/go-jira/jira. Hence we set up a config.yml to point users toward this project. We did not receive them for a while. Thats why we are removing this reference. * GitHub Actions: Rename "test and lint" to "unit tests" --- .github/ISSUE_TEMPLATE/config.yml | 4 --- .github/workflows/testing.yml | 42 ++++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 10 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 0dcdd1f..0000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,4 +0,0 @@ -contact_links: - - name: Report a bug/feature request for the Jira Command Line Client - url: https://github.com/go-jira/jira/issues - about: This is the issue tracker for the Jira command-line client in Go. If you are using this, please report issues there. \ No newline at end of file diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 6d7699f..81e1672 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -10,8 +10,8 @@ on: - cron: "5 1 * * *" jobs: - test: - name: Test and lint + unit-test: + name: Unit tests strategy: fail-fast: false matrix: @@ -25,19 +25,49 @@ jobs: with: go-version: ${{ matrix.go }} + - name: Run Unit tests (Go ${{ matrix.go }}) + run: make test + + fmt: + name: go fmt + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: 1.19 + - name: Run go fmt (Go ${{ matrix.go }}) if: runner.os != 'Windows' run: diff -u <(echo -n) <(gofmt -d -s .) + vet: + name: go vet + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: 1.19 + - name: Run go vet run: make vet + staticcheck: + name: staticcheck + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: 1.19 + - name: Run staticcheck (Go ${{ matrix.go }}) uses: dominikh/staticcheck-action@v1.2.0 with: version: "2022.1" install-go: false - cache-key: ${{ matrix.go }} - - - name: Run Unit tests (Go ${{ matrix.go }}) - run: make test + cache-key: staticcheck-cache \ No newline at end of file From a879a187a3292e013d53064bc17c0cd8cf057bf5 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 18:43:48 +0200 Subject: [PATCH 017/189] Changelog: Add note about ...WithContext API methods --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2429f69..134755b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -135,11 +135,44 @@ Like client.NewMultiPartRequest(context.Background(), "GET", .....) ``` +#### `context` is a first class citizen + +All API methods require a `context` as first argument. + +In the v1, some methods had a `...WithContext` suffix. +These methods have been removed. + +If you used a service like + +```go +client.Issue.CreateWithContext(ctx, ...) +``` + +the new call would be + +```go +client.Issue.Create(ctx, ...) +``` + +If you used API calls without a context, like + +```go +client.Issue.Create(...) +``` + +the new call would be + +```go +client.Issue.Create(ctx, ...) +``` + ### Breaking changes * Jira On-Premise and Jira Cloud have now different clients, because the API differs * `client.NewRawRequestWithContext()` has been removed in favor of `client.NewRawRequest()`, which requires now a context as first argument * `client.NewRequestWithContext()` has been removed in favor of `client.NewRequest()`, which requires now a context as first argument +* `client.NewMultiPartRequestWithContext()` has been removed in favor of `client.NewMultiPartRequest()`, which requires now a context as first argument +* `context` is now a first class citizen in all API calls. Functions that had a suffix like `...WithContext` have been removed entirely. The API methods support the context now as first argument. ### Features From c0f71ddcce25ee4cff22163bb1c88b04f226a253 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 18:44:18 +0200 Subject: [PATCH 018/189] Version Service: Remove "WithContext" API methods --- cloud/version.go | 28 ++++++---------------------- cloud/version_test.go | 7 ++++--- onpremise/version.go | 28 ++++++---------------------- onpremise/version_test.go | 7 ++++--- 4 files changed, 20 insertions(+), 50 deletions(-) diff --git a/cloud/version.go b/cloud/version.go index 69b4bac..aaad00b 100644 --- a/cloud/version.go +++ b/cloud/version.go @@ -26,10 +26,10 @@ type Version struct { StartDate string `json:"startDate,omitempty" structs:"startDate,omitempty"` } -// GetWithContext gets version info from Jira +// Get gets version info from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-get -func (s *VersionService) GetWithContext(ctx context.Context, versionID int) (*Version, *Response, error) { +func (s *VersionService) Get(ctx context.Context, versionID int) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/version/%v", versionID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -44,15 +44,10 @@ func (s *VersionService) GetWithContext(ctx context.Context, versionID int) (*Ve return version, resp, nil } -// Get wraps GetWithContext using the background context. -func (s *VersionService) Get(versionID int) (*Version, *Response, error) { - return s.GetWithContext(context.Background(), versionID) -} - -// CreateWithContext creates a version in Jira. +// Create creates a version in Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-post -func (s *VersionService) CreateWithContext(ctx context.Context, version *Version) (*Version, *Response, error) { +func (s *VersionService) Create(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := "/rest/api/2/version" req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, version) if err != nil { @@ -79,16 +74,11 @@ func (s *VersionService) CreateWithContext(ctx context.Context, version *Version return responseVersion, resp, nil } -// Create wraps CreateWithContext using the background context. -func (s *VersionService) Create(version *Version) (*Version, *Response, error) { - return s.CreateWithContext(context.Background(), version) -} - -// UpdateWithContext updates a version from a JSON representation. +// Update updates a version from a JSON representation. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-put // Caller must close resp.Body -func (s *VersionService) UpdateWithContext(ctx context.Context, version *Version) (*Version, *Response, error) { +func (s *VersionService) Update(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/version/%v", version.ID) req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, version) if err != nil { @@ -105,9 +95,3 @@ func (s *VersionService) UpdateWithContext(ctx context.Context, version *Version ret := *version return &ret, resp, nil } - -// Update wraps UpdateWithContext using the background context. -// Caller must close resp.Body -func (s *VersionService) Update(version *Version) (*Version, *Response, error) { - return s.UpdateWithContext(context.Background(), version) -} diff --git a/cloud/version_test.go b/cloud/version_test.go index 3e89c11..3cf52d0 100644 --- a/cloud/version_test.go +++ b/cloud/version_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "testing" @@ -28,7 +29,7 @@ func TestVersionService_Get_Success(t *testing.T) { }`) }) - version, _, err := testClient.Version.Get(10002) + version, _, err := testClient.Version.Get(context.Background(), 10002) if version == nil { t.Error("Expected version. Issue is nil") } @@ -68,7 +69,7 @@ func TestVersionService_Create(t *testing.T) { StartDate: "2018-07-01", } - version, _, err := testClient.Version.Create(v) + version, _, err := testClient.Version.Create(context.Background(), v) if version == nil { t.Error("Expected version. Version is nil") } @@ -102,7 +103,7 @@ func TestServiceService_Update(t *testing.T) { Description: "An excellent updated version", } - version, _, err := testClient.Version.Update(v) + version, _, err := testClient.Version.Update(context.Background(), v) if version == nil { t.Error("Expected version. Version is nil") } diff --git a/onpremise/version.go b/onpremise/version.go index ec4ce2c..d1edc02 100644 --- a/onpremise/version.go +++ b/onpremise/version.go @@ -26,10 +26,10 @@ type Version struct { StartDate string `json:"startDate,omitempty" structs:"startDate,omitempty"` } -// GetWithContext gets version info from Jira +// Get gets version info from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-get -func (s *VersionService) GetWithContext(ctx context.Context, versionID int) (*Version, *Response, error) { +func (s *VersionService) Get(ctx context.Context, versionID int) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/version/%v", versionID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -44,15 +44,10 @@ func (s *VersionService) GetWithContext(ctx context.Context, versionID int) (*Ve return version, resp, nil } -// Get wraps GetWithContext using the background context. -func (s *VersionService) Get(versionID int) (*Version, *Response, error) { - return s.GetWithContext(context.Background(), versionID) -} - -// CreateWithContext creates a version in Jira. +// Create creates a version in Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-post -func (s *VersionService) CreateWithContext(ctx context.Context, version *Version) (*Version, *Response, error) { +func (s *VersionService) Create(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := "/rest/api/2/version" req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, version) if err != nil { @@ -79,16 +74,11 @@ func (s *VersionService) CreateWithContext(ctx context.Context, version *Version return responseVersion, resp, nil } -// Create wraps CreateWithContext using the background context. -func (s *VersionService) Create(version *Version) (*Version, *Response, error) { - return s.CreateWithContext(context.Background(), version) -} - -// UpdateWithContext updates a version from a JSON representation. +// Update updates a version from a JSON representation. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-put // Caller must close resp.Body -func (s *VersionService) UpdateWithContext(ctx context.Context, version *Version) (*Version, *Response, error) { +func (s *VersionService) Update(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/version/%v", version.ID) req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, version) if err != nil { @@ -105,9 +95,3 @@ func (s *VersionService) UpdateWithContext(ctx context.Context, version *Version ret := *version return &ret, resp, nil } - -// Update wraps UpdateWithContext using the background context. -// Caller must close resp.Body -func (s *VersionService) Update(version *Version) (*Version, *Response, error) { - return s.UpdateWithContext(context.Background(), version) -} diff --git a/onpremise/version_test.go b/onpremise/version_test.go index 7ec3ca4..a43f14d 100644 --- a/onpremise/version_test.go +++ b/onpremise/version_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "testing" @@ -28,7 +29,7 @@ func TestVersionService_Get_Success(t *testing.T) { }`) }) - version, _, err := testClient.Version.Get(10002) + version, _, err := testClient.Version.Get(context.Background(), 10002) if version == nil { t.Error("Expected version. Issue is nil") } @@ -68,7 +69,7 @@ func TestVersionService_Create(t *testing.T) { StartDate: "2018-07-01", } - version, _, err := testClient.Version.Create(v) + version, _, err := testClient.Version.Create(context.Background(), v) if version == nil { t.Error("Expected version. Version is nil") } @@ -102,7 +103,7 @@ func TestServiceService_Update(t *testing.T) { Description: "An excellent updated version", } - version, _, err := testClient.Version.Update(v) + version, _, err := testClient.Version.Update(context.Background(), v) if version == nil { t.Error("Expected version. Version is nil") } From 9d33bf40e4408cb0307c2eada5696dd31ac2e57a Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 18:52:27 +0200 Subject: [PATCH 019/189] Fix import pathes of onpremise examples --- onpremise/examples/addlabel/main.go | 2 +- onpremise/examples/basicauth/main.go | 2 +- onpremise/examples/create/main.go | 2 +- onpremise/examples/createwithcustomfields/main.go | 2 +- onpremise/examples/do/main.go | 2 +- onpremise/examples/ignorecerts/main.go | 2 +- onpremise/examples/jql/main.go | 2 +- onpremise/examples/newclient/main.go | 2 +- onpremise/examples/pagination/main.go | 2 +- onpremise/examples/renderedfields/main.go | 2 +- onpremise/examples/searchpages/main.go | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/onpremise/examples/addlabel/main.go b/onpremise/examples/addlabel/main.go index ca5a6f7..64c2c4d 100644 --- a/onpremise/examples/addlabel/main.go +++ b/onpremise/examples/addlabel/main.go @@ -8,7 +8,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/onpremise" "golang.org/x/term" ) diff --git a/onpremise/examples/basicauth/main.go b/onpremise/examples/basicauth/main.go index 7221d87..e955171 100644 --- a/onpremise/examples/basicauth/main.go +++ b/onpremise/examples/basicauth/main.go @@ -9,7 +9,7 @@ import ( "golang.org/x/term" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/onpremise" ) func main() { diff --git a/onpremise/examples/create/main.go b/onpremise/examples/create/main.go index 2dc3837..e1b8d60 100644 --- a/onpremise/examples/create/main.go +++ b/onpremise/examples/create/main.go @@ -7,7 +7,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/onpremise" "golang.org/x/term" ) diff --git a/onpremise/examples/createwithcustomfields/main.go b/onpremise/examples/createwithcustomfields/main.go index 68b32b5..3d6e510 100644 --- a/onpremise/examples/createwithcustomfields/main.go +++ b/onpremise/examples/createwithcustomfields/main.go @@ -7,7 +7,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/onpremise" "github.com/trivago/tgo/tcontainer" "golang.org/x/term" ) diff --git a/onpremise/examples/do/main.go b/onpremise/examples/do/main.go index eee2747..ee9c15f 100644 --- a/onpremise/examples/do/main.go +++ b/onpremise/examples/do/main.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/onpremise" ) func main() { diff --git a/onpremise/examples/ignorecerts/main.go b/onpremise/examples/ignorecerts/main.go index 525cd51..c1ccc14 100644 --- a/onpremise/examples/ignorecerts/main.go +++ b/onpremise/examples/ignorecerts/main.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/onpremise" ) func main() { diff --git a/onpremise/examples/jql/main.go b/onpremise/examples/jql/main.go index ce3ba45..dfb65dc 100644 --- a/onpremise/examples/jql/main.go +++ b/onpremise/examples/jql/main.go @@ -3,7 +3,7 @@ package main import ( "fmt" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/onpremise" ) func main() { diff --git a/onpremise/examples/newclient/main.go b/onpremise/examples/newclient/main.go index c03461d..17bba15 100644 --- a/onpremise/examples/newclient/main.go +++ b/onpremise/examples/newclient/main.go @@ -3,7 +3,7 @@ package main import ( "fmt" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/onpremise" ) func main() { diff --git a/onpremise/examples/pagination/main.go b/onpremise/examples/pagination/main.go index 07c52ee..ab2bb36 100644 --- a/onpremise/examples/pagination/main.go +++ b/onpremise/examples/pagination/main.go @@ -3,7 +3,7 @@ package main import ( "fmt" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/onpremise" ) // GetAllIssues will implement pagination of api and get all the issues. diff --git a/onpremise/examples/renderedfields/main.go b/onpremise/examples/renderedfields/main.go index 274498f..e979b47 100644 --- a/onpremise/examples/renderedfields/main.go +++ b/onpremise/examples/renderedfields/main.go @@ -10,7 +10,7 @@ import ( "golang.org/x/term" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/onpremise" ) func main() { diff --git a/onpremise/examples/searchpages/main.go b/onpremise/examples/searchpages/main.go index 51db937..82bb3ec 100644 --- a/onpremise/examples/searchpages/main.go +++ b/onpremise/examples/searchpages/main.go @@ -9,7 +9,7 @@ import ( "syscall" "time" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/onpremise" "golang.org/x/term" ) From 62f4b5d5e527165c31d27f07f82719ae4ba7a95f Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 18:54:54 +0200 Subject: [PATCH 020/189] User Service: Remove "WithContext" API methods --- ...th_transport_personal_access_token_test.go | 3 +- cloud/examples/basicauth/main.go | 3 +- cloud/issue.go | 2 +- cloud/user.go | 64 ++++--------------- cloud/user_test.go | 17 ++--- ...th_transport_personal_access_token_test.go | 3 +- onpremise/examples/basicauth/main.go | 3 +- onpremise/issue.go | 2 +- onpremise/user.go | 64 ++++--------------- onpremise/user_test.go | 17 ++--- 10 files changed, 56 insertions(+), 122 deletions(-) diff --git a/cloud/auth_transport_personal_access_token_test.go b/cloud/auth_transport_personal_access_token_test.go index 397a8e8..d240315 100644 --- a/cloud/auth_transport_personal_access_token_test.go +++ b/cloud/auth_transport_personal_access_token_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "net/http" "testing" ) @@ -24,6 +25,6 @@ func TestPATAuthTransport_HeaderContainsAuth(t *testing.T) { }) client, _ := NewClient(testServer.URL, patTransport.Client()) - client.User.GetSelf() + client.User.GetSelf(context.Background()) } diff --git a/cloud/examples/basicauth/main.go b/cloud/examples/basicauth/main.go index 7221d87..7aa2116 100644 --- a/cloud/examples/basicauth/main.go +++ b/cloud/examples/basicauth/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "context" "fmt" "os" "strings" @@ -36,7 +37,7 @@ func main() { return } - u, _, err := client.User.Get("admin") + u, _, err := client.User.Get(context.Background(), "admin") if err != nil { fmt.Printf("\nerror: %v\n", err) diff --git a/cloud/issue.go b/cloud/issue.go index 2ee215f..8570060 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -1418,7 +1418,7 @@ func (s *IssueService) GetWatchersWithContext(ctx context.Context, issueID strin for _, watcher := range watches.Watchers { var user *User if watcher.AccountID != "" { - user, resp, err = s.client.User.GetByAccountID(watcher.AccountID) + user, resp, err = s.client.User.GetByAccountID(context.Background(), watcher.AccountID) if err != nil { return nil, resp, NewJiraError(resp, err) } diff --git a/cloud/user.go b/cloud/user.go index acd038c..ade589c 100644 --- a/cloud/user.go +++ b/cloud/user.go @@ -44,10 +44,10 @@ type userSearch []userSearchParam type userSearchF func(userSearch) userSearch -// GetWithContext gets user info from Jira using its Account Id +// Get gets user info from Jira using its Account Id // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-get -func (s *UserService) GetWithContext(ctx context.Context, accountId string) (*User, *Response, error) { +func (s *UserService) Get(ctx context.Context, accountId string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -62,16 +62,11 @@ func (s *UserService) GetWithContext(ctx context.Context, accountId string) (*Us return user, resp, nil } -// Get wraps GetWithContext using the background context. -func (s *UserService) Get(accountId string) (*User, *Response, error) { - return s.GetWithContext(context.Background(), accountId) -} - -// GetByAccountIDWithContext gets user info from Jira +// GetByAccountID gets user info from Jira // Searching by another parameter that is not accountId is deprecated, // but this method is kept for backwards compatibility // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser -func (s *UserService) GetByAccountIDWithContext(ctx context.Context, accountID string) (*User, *Response, error) { +func (s *UserService) GetByAccountID(ctx context.Context, accountID string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -86,15 +81,10 @@ func (s *UserService) GetByAccountIDWithContext(ctx context.Context, accountID s return user, resp, nil } -// GetByAccountID wraps GetByAccountIDWithContext using the background context. -func (s *UserService) GetByAccountID(accountID string) (*User, *Response, error) { - return s.GetByAccountIDWithContext(context.Background(), accountID) -} - -// CreateWithContext creates an user in Jira. +// Create creates an user in Jira. // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-createUser -func (s *UserService) CreateWithContext(ctx context.Context, user *User) (*User, *Response, error) { +func (s *UserService) Create(ctx context.Context, user *User) (*User, *Response, error) { apiEndpoint := "/rest/api/2/user" req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, user) if err != nil { @@ -121,17 +111,12 @@ func (s *UserService) CreateWithContext(ctx context.Context, user *User) (*User, return responseUser, resp, nil } -// Create wraps CreateWithContext using the background context. -func (s *UserService) Create(user *User) (*User, *Response, error) { - return s.CreateWithContext(context.Background(), user) -} - -// DeleteWithContext deletes an user from Jira. +// Delete deletes an user from Jira. // Returns http.StatusNoContent on success. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-delete // Caller must close resp.Body -func (s *UserService) DeleteWithContext(ctx context.Context, accountId string) (*Response, error) { +func (s *UserService) Delete(ctx context.Context, accountId string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { @@ -145,16 +130,10 @@ func (s *UserService) DeleteWithContext(ctx context.Context, accountId string) ( return resp, nil } -// Delete wraps DeleteWithContext using the background context. -// Caller must close resp.Body -func (s *UserService) Delete(accountId string) (*Response, error) { - return s.DeleteWithContext(context.Background(), accountId) -} - -// GetGroupsWithContext returns the groups which the user belongs to +// GetGroups returns the groups which the user belongs to // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-groups-get -func (s *UserService) GetGroupsWithContext(ctx context.Context, accountId string) (*[]UserGroup, *Response, error) { +func (s *UserService) GetGroups(ctx context.Context, accountId string) (*[]UserGroup, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?accountId=%s", accountId) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -169,15 +148,10 @@ func (s *UserService) GetGroupsWithContext(ctx context.Context, accountId string return userGroups, resp, nil } -// GetGroups wraps GetGroupsWithContext using the background context. -func (s *UserService) GetGroups(accountId string) (*[]UserGroup, *Response, error) { - return s.GetGroupsWithContext(context.Background(), accountId) -} - -// GetSelfWithContext information about the current logged-in user +// GetSelf information about the current logged-in user // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-myself-get -func (s *UserService) GetSelfWithContext(ctx context.Context) (*User, *Response, error) { +func (s *UserService) GetSelf(ctx context.Context) (*User, *Response, error) { const apiEndpoint = "rest/api/2/myself" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -191,11 +165,6 @@ func (s *UserService) GetSelfWithContext(ctx context.Context) (*User, *Response, return &user, resp, nil } -// GetSelf wraps GetSelfWithContext using the background context. -func (s *UserService) GetSelf() (*User, *Response, error) { - return s.GetSelfWithContext(context.Background()) -} - // WithMaxResults sets the max results to return func WithMaxResults(maxResults int) userSearchF { return func(s userSearch) userSearch { @@ -252,11 +221,11 @@ func WithProperty(property string) userSearchF { } } -// FindWithContext searches for user info from Jira: +// Find searches for user info from Jira: // It can find users by email or display name using the query parameter // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-search-get -func (s *UserService) FindWithContext(ctx context.Context, property string, tweaks ...userSearchF) ([]User, *Response, error) { +func (s *UserService) Find(ctx context.Context, property string, tweaks ...userSearchF) ([]User, *Response, error) { search := []userSearchParam{ { name: "query", @@ -285,8 +254,3 @@ func (s *UserService) FindWithContext(ctx context.Context, property string, twea } return users, resp, nil } - -// Find wraps FindWithContext using the background context. -func (s *UserService) Find(property string, tweaks ...userSearchF) ([]User, *Response, error) { - return s.FindWithContext(context.Background(), property, tweaks...) -} diff --git a/cloud/user_test.go b/cloud/user_test.go index f170e8a..5964874 100644 --- a/cloud/user_test.go +++ b/cloud/user_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "testing" @@ -22,7 +23,7 @@ func TestUserService_Get_Success(t *testing.T) { }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) }) - if user, _, err := testClient.User.Get("000000000000000000000000"); err != nil { + if user, _, err := testClient.User.Get(context.Background(), "000000000000000000000000"); err != nil { t.Errorf("Error given: %s", err) } else if user == nil { t.Error("Expected user. User is nil") @@ -45,7 +46,7 @@ func TestUserService_GetByAccountID_Success(t *testing.T) { }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) }) - if user, _, err := testClient.User.GetByAccountID("000000000000000000000000"); err != nil { + if user, _, err := testClient.User.GetByAccountID(context.Background(), "000000000000000000000000"); err != nil { t.Errorf("Error given: %s", err) } else if user == nil { t.Error("Expected user. User is nil") @@ -72,7 +73,7 @@ func TestUserService_Create(t *testing.T) { ApplicationKeys: []string{"jira-core"}, } - if user, _, err := testClient.User.Create(u); err != nil { + if user, _, err := testClient.User.Create(context.Background(), u); err != nil { t.Errorf("Error given: %s", err) } else if user == nil { t.Error("Expected user. User is nil") @@ -89,7 +90,7 @@ func TestUserService_Delete(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - resp, err := testClient.User.Delete("000000000000000000000000") + resp, err := testClient.User.Delete(context.Background(), "000000000000000000000000") if err != nil { t.Errorf("Error given: %s", err) } @@ -110,7 +111,7 @@ func TestUserService_GetGroups(t *testing.T) { fmt.Fprint(w, `[{"name":"jira-software-users","self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000"}]`) }) - if groups, _, err := testClient.User.GetGroups("000000000000000000000000"); err != nil { + if groups, _, err := testClient.User.GetGroups(context.Background(), "000000000000000000000000"); err != nil { t.Errorf("Error given: %s", err) } else if groups == nil { t.Error("Expected user groups. []UserGroup is nil") @@ -134,7 +135,7 @@ func TestUserService_GetSelf(t *testing.T) { }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) }) - if user, _, err := testClient.User.GetSelf(); err != nil { + if user, _, err := testClient.User.GetSelf(context.Background()); err != nil { t.Errorf("Error given: %s", err) } else if user == nil { t.Error("Expected user groups. []UserGroup is nil") @@ -161,7 +162,7 @@ func TestUserService_Find_Success(t *testing.T) { }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}]`) }) - if user, _, err := testClient.User.Find("fred@example.com"); err != nil { + if user, _, err := testClient.User.Find(context.Background(), "fred@example.com"); err != nil { t.Errorf("Error given: %s", err) } else if user == nil { t.Error("Expected user. User is nil") @@ -184,7 +185,7 @@ func TestUserService_Find_SuccessParams(t *testing.T) { }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}]`) }) - if user, _, err := testClient.User.Find("fred@example.com", WithStartAt(100), WithMaxResults(1000)); err != nil { + if user, _, err := testClient.User.Find(context.Background(), "fred@example.com", WithStartAt(100), WithMaxResults(1000)); err != nil { t.Errorf("Error given: %s", err) } else if user == nil { t.Error("Expected user. User is nil") diff --git a/onpremise/auth_transport_personal_access_token_test.go b/onpremise/auth_transport_personal_access_token_test.go index b205a5f..bf04798 100644 --- a/onpremise/auth_transport_personal_access_token_test.go +++ b/onpremise/auth_transport_personal_access_token_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "net/http" "testing" ) @@ -24,6 +25,6 @@ func TestPATAuthTransport_HeaderContainsAuth(t *testing.T) { }) client, _ := NewClient(testServer.URL, patTransport.Client()) - client.User.GetSelf() + client.User.GetSelf(context.Background()) } diff --git a/onpremise/examples/basicauth/main.go b/onpremise/examples/basicauth/main.go index e955171..127c55e 100644 --- a/onpremise/examples/basicauth/main.go +++ b/onpremise/examples/basicauth/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "context" "fmt" "os" "strings" @@ -36,7 +37,7 @@ func main() { return } - u, _, err := client.User.Get("admin") + u, _, err := client.User.Get(context.Background(), "admin") if err != nil { fmt.Printf("\nerror: %v\n", err) diff --git a/onpremise/issue.go b/onpremise/issue.go index eb08747..f4b54d9 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -1418,7 +1418,7 @@ func (s *IssueService) GetWatchersWithContext(ctx context.Context, issueID strin for _, watcher := range watches.Watchers { var user *User if watcher.AccountID != "" { - user, resp, err = s.client.User.GetByAccountID(watcher.AccountID) + user, resp, err = s.client.User.GetByAccountID(context.Background(), watcher.AccountID) if err != nil { return nil, resp, NewJiraError(resp, err) } diff --git a/onpremise/user.go b/onpremise/user.go index 1064c0e..79003be 100644 --- a/onpremise/user.go +++ b/onpremise/user.go @@ -44,10 +44,10 @@ type userSearch []userSearchParam type userSearchF func(userSearch) userSearch -// GetWithContext gets user info from Jira using its Account Id +// Get gets user info from Jira using its Account Id // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-get -func (s *UserService) GetWithContext(ctx context.Context, accountId string) (*User, *Response, error) { +func (s *UserService) Get(ctx context.Context, accountId string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -62,16 +62,11 @@ func (s *UserService) GetWithContext(ctx context.Context, accountId string) (*Us return user, resp, nil } -// Get wraps GetWithContext using the background context. -func (s *UserService) Get(accountId string) (*User, *Response, error) { - return s.GetWithContext(context.Background(), accountId) -} - -// GetByAccountIDWithContext gets user info from Jira +// GetByAccountID gets user info from Jira // Searching by another parameter that is not accountId is deprecated, // but this method is kept for backwards compatibility // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser -func (s *UserService) GetByAccountIDWithContext(ctx context.Context, accountID string) (*User, *Response, error) { +func (s *UserService) GetByAccountID(ctx context.Context, accountID string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -86,15 +81,10 @@ func (s *UserService) GetByAccountIDWithContext(ctx context.Context, accountID s return user, resp, nil } -// GetByAccountID wraps GetByAccountIDWithContext using the background context. -func (s *UserService) GetByAccountID(accountID string) (*User, *Response, error) { - return s.GetByAccountIDWithContext(context.Background(), accountID) -} - -// CreateWithContext creates an user in Jira. +// Create creates an user in Jira. // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-createUser -func (s *UserService) CreateWithContext(ctx context.Context, user *User) (*User, *Response, error) { +func (s *UserService) Create(ctx context.Context, user *User) (*User, *Response, error) { apiEndpoint := "/rest/api/2/user" req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, user) if err != nil { @@ -121,17 +111,12 @@ func (s *UserService) CreateWithContext(ctx context.Context, user *User) (*User, return responseUser, resp, nil } -// Create wraps CreateWithContext using the background context. -func (s *UserService) Create(user *User) (*User, *Response, error) { - return s.CreateWithContext(context.Background(), user) -} - -// DeleteWithContext deletes an user from Jira. +// Delete deletes an user from Jira. // Returns http.StatusNoContent on success. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-delete // Caller must close resp.Body -func (s *UserService) DeleteWithContext(ctx context.Context, accountId string) (*Response, error) { +func (s *UserService) Delete(ctx context.Context, accountId string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { @@ -145,16 +130,10 @@ func (s *UserService) DeleteWithContext(ctx context.Context, accountId string) ( return resp, nil } -// Delete wraps DeleteWithContext using the background context. -// Caller must close resp.Body -func (s *UserService) Delete(accountId string) (*Response, error) { - return s.DeleteWithContext(context.Background(), accountId) -} - -// GetGroupsWithContext returns the groups which the user belongs to +// GetGroups returns the groups which the user belongs to // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-groups-get -func (s *UserService) GetGroupsWithContext(ctx context.Context, accountId string) (*[]UserGroup, *Response, error) { +func (s *UserService) GetGroups(ctx context.Context, accountId string) (*[]UserGroup, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?accountId=%s", accountId) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -169,15 +148,10 @@ func (s *UserService) GetGroupsWithContext(ctx context.Context, accountId string return userGroups, resp, nil } -// GetGroups wraps GetGroupsWithContext using the background context. -func (s *UserService) GetGroups(accountId string) (*[]UserGroup, *Response, error) { - return s.GetGroupsWithContext(context.Background(), accountId) -} - -// GetSelfWithContext information about the current logged-in user +// GetSelf information about the current logged-in user // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-myself-get -func (s *UserService) GetSelfWithContext(ctx context.Context) (*User, *Response, error) { +func (s *UserService) GetSelf(ctx context.Context) (*User, *Response, error) { const apiEndpoint = "rest/api/2/myself" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -191,11 +165,6 @@ func (s *UserService) GetSelfWithContext(ctx context.Context) (*User, *Response, return &user, resp, nil } -// GetSelf wraps GetSelfWithContext using the background context. -func (s *UserService) GetSelf() (*User, *Response, error) { - return s.GetSelfWithContext(context.Background()) -} - // WithMaxResults sets the max results to return func WithMaxResults(maxResults int) userSearchF { return func(s userSearch) userSearch { @@ -252,11 +221,11 @@ func WithProperty(property string) userSearchF { } } -// FindWithContext searches for user info from Jira: +// Find searches for user info from Jira: // It can find users by email or display name using the query parameter // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-search-get -func (s *UserService) FindWithContext(ctx context.Context, property string, tweaks ...userSearchF) ([]User, *Response, error) { +func (s *UserService) Find(ctx context.Context, property string, tweaks ...userSearchF) ([]User, *Response, error) { search := []userSearchParam{ { name: "query", @@ -285,8 +254,3 @@ func (s *UserService) FindWithContext(ctx context.Context, property string, twea } return users, resp, nil } - -// Find wraps FindWithContext using the background context. -func (s *UserService) Find(property string, tweaks ...userSearchF) ([]User, *Response, error) { - return s.FindWithContext(context.Background(), property, tweaks...) -} diff --git a/onpremise/user_test.go b/onpremise/user_test.go index f76d22c..97a4d39 100644 --- a/onpremise/user_test.go +++ b/onpremise/user_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "testing" @@ -22,7 +23,7 @@ func TestUserService_Get_Success(t *testing.T) { }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) }) - if user, _, err := testClient.User.Get("000000000000000000000000"); err != nil { + if user, _, err := testClient.User.Get(context.Background(), "000000000000000000000000"); err != nil { t.Errorf("Error given: %s", err) } else if user == nil { t.Error("Expected user. User is nil") @@ -45,7 +46,7 @@ func TestUserService_GetByAccountID_Success(t *testing.T) { }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) }) - if user, _, err := testClient.User.GetByAccountID("000000000000000000000000"); err != nil { + if user, _, err := testClient.User.GetByAccountID(context.Background(), "000000000000000000000000"); err != nil { t.Errorf("Error given: %s", err) } else if user == nil { t.Error("Expected user. User is nil") @@ -72,7 +73,7 @@ func TestUserService_Create(t *testing.T) { ApplicationKeys: []string{"jira-core"}, } - if user, _, err := testClient.User.Create(u); err != nil { + if user, _, err := testClient.User.Create(context.Background(), u); err != nil { t.Errorf("Error given: %s", err) } else if user == nil { t.Error("Expected user. User is nil") @@ -89,7 +90,7 @@ func TestUserService_Delete(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - resp, err := testClient.User.Delete("000000000000000000000000") + resp, err := testClient.User.Delete(context.Background(), "000000000000000000000000") if err != nil { t.Errorf("Error given: %s", err) } @@ -110,7 +111,7 @@ func TestUserService_GetGroups(t *testing.T) { fmt.Fprint(w, `[{"name":"jira-software-users","self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000"}]`) }) - if groups, _, err := testClient.User.GetGroups("000000000000000000000000"); err != nil { + if groups, _, err := testClient.User.GetGroups(context.Background(), "000000000000000000000000"); err != nil { t.Errorf("Error given: %s", err) } else if groups == nil { t.Error("Expected user groups. []UserGroup is nil") @@ -134,7 +135,7 @@ func TestUserService_GetSelf(t *testing.T) { }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) }) - if user, _, err := testClient.User.GetSelf(); err != nil { + if user, _, err := testClient.User.GetSelf(context.Background()); err != nil { t.Errorf("Error given: %s", err) } else if user == nil { t.Error("Expected user groups. []UserGroup is nil") @@ -161,7 +162,7 @@ func TestUserService_Find_Success(t *testing.T) { }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}]`) }) - if user, _, err := testClient.User.Find("fred@example.com"); err != nil { + if user, _, err := testClient.User.Find(context.Background(), "fred@example.com"); err != nil { t.Errorf("Error given: %s", err) } else if user == nil { t.Error("Expected user. User is nil") @@ -184,7 +185,7 @@ func TestUserService_Find_SuccessParams(t *testing.T) { }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}]`) }) - if user, _, err := testClient.User.Find("fred@example.com", WithStartAt(100), WithMaxResults(1000)); err != nil { + if user, _, err := testClient.User.Find(context.Background(), "fred@example.com", WithStartAt(100), WithMaxResults(1000)); err != nil { t.Errorf("Error given: %s", err) } else if user == nil { t.Error("Expected user. User is nil") From 28be191ba68bad54500de9fe167c7ea15ab49b32 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 18:57:00 +0200 Subject: [PATCH 021/189] Status Category Service: Remove "WithContext" API methods --- cloud/statuscategory.go | 9 ++------- cloud/statuscategory_test.go | 3 ++- onpremise/statuscategory.go | 9 ++------- onpremise/statuscategory_test.go | 3 ++- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/cloud/statuscategory.go b/cloud/statuscategory.go index d7da81c..eea97a4 100644 --- a/cloud/statuscategory.go +++ b/cloud/statuscategory.go @@ -25,10 +25,10 @@ const ( StatusCategoryUndefined = "undefined" ) -// GetListWithContext gets all status categories from Jira +// GetList gets all status categories from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get -func (s *StatusCategoryService) GetListWithContext(ctx context.Context) ([]StatusCategory, *Response, error) { +func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, *Response, error) { apiEndpoint := "rest/api/2/statuscategory" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -42,8 +42,3 @@ func (s *StatusCategoryService) GetListWithContext(ctx context.Context) ([]Statu } return statusCategoryList, resp, nil } - -// GetList wraps GetListWithContext using the background context. -func (s *StatusCategoryService) GetList() ([]StatusCategory, *Response, error) { - return s.GetListWithContext(context.Background()) -} diff --git a/cloud/statuscategory_test.go b/cloud/statuscategory_test.go index 9632a0d..cad63fe 100644 --- a/cloud/statuscategory_test.go +++ b/cloud/statuscategory_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestStatusCategoryService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - statusCategory, _, err := testClient.StatusCategory.GetList() + statusCategory, _, err := testClient.StatusCategory.GetList(context.Background()) if statusCategory == nil { t.Error("Expected statusCategory list. StatusCategory list is nil") } diff --git a/onpremise/statuscategory.go b/onpremise/statuscategory.go index 2035d7a..7e7c8ce 100644 --- a/onpremise/statuscategory.go +++ b/onpremise/statuscategory.go @@ -25,10 +25,10 @@ const ( StatusCategoryUndefined = "undefined" ) -// GetListWithContext gets all status categories from Jira +// GetList gets all status categories from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get -func (s *StatusCategoryService) GetListWithContext(ctx context.Context) ([]StatusCategory, *Response, error) { +func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, *Response, error) { apiEndpoint := "rest/api/2/statuscategory" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -42,8 +42,3 @@ func (s *StatusCategoryService) GetListWithContext(ctx context.Context) ([]Statu } return statusCategoryList, resp, nil } - -// GetList wraps GetListWithContext using the background context. -func (s *StatusCategoryService) GetList() ([]StatusCategory, *Response, error) { - return s.GetListWithContext(context.Background()) -} diff --git a/onpremise/statuscategory_test.go b/onpremise/statuscategory_test.go index e06e717..aee35ef 100644 --- a/onpremise/statuscategory_test.go +++ b/onpremise/statuscategory_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestStatusCategoryService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - statusCategory, _, err := testClient.StatusCategory.GetList() + statusCategory, _, err := testClient.StatusCategory.GetList(context.Background()) if statusCategory == nil { t.Error("Expected statusCategory list. StatusCategory list is nil") } From b6aa52c13a8e29a0453c635ea3d71bbf05e7f612 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 18:58:35 +0200 Subject: [PATCH 022/189] Status Service: Remove "WithContext" API methods --- cloud/status.go | 9 ++------- cloud/status_test.go | 3 ++- onpremise/status.go | 9 ++------- onpremise/status_test.go | 3 ++- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/cloud/status.go b/cloud/status.go index 181d8ac..8d293d5 100644 --- a/cloud/status.go +++ b/cloud/status.go @@ -19,10 +19,10 @@ type Status struct { StatusCategory StatusCategory `json:"statusCategory" structs:"statusCategory"` } -// GetAllStatusesWithContext returns a list of all statuses associated with workflows. +// GetAllStatuses returns a list of all statuses associated with workflows. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-status-get -func (s *StatusService) GetAllStatusesWithContext(ctx context.Context) ([]Status, *Response, error) { +func (s *StatusService) GetAllStatuses(ctx context.Context) ([]Status, *Response, error) { apiEndpoint := "rest/api/2/status" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) @@ -38,8 +38,3 @@ func (s *StatusService) GetAllStatusesWithContext(ctx context.Context) ([]Status return statusList, resp, nil } - -// GetAllStatuses wraps GetAllStatusesWithContext using the background context. -func (s *StatusService) GetAllStatuses() ([]Status, *Response, error) { - return s.GetAllStatusesWithContext(context.Background()) -} diff --git a/cloud/status_test.go b/cloud/status_test.go index 60aa096..1e71957 100644 --- a/cloud/status_test.go +++ b/cloud/status_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "os" @@ -23,7 +24,7 @@ func TestStatusService_GetAllStatuses(t *testing.T) { fmt.Fprint(w, string(raw)) }) - statusList, _, err := testClient.Status.GetAllStatuses() + statusList, _, err := testClient.Status.GetAllStatuses(context.Background()) if statusList == nil { t.Error("Expected statusList. statusList is nill") diff --git a/onpremise/status.go b/onpremise/status.go index 0ca307f..f3e94f2 100644 --- a/onpremise/status.go +++ b/onpremise/status.go @@ -19,10 +19,10 @@ type Status struct { StatusCategory StatusCategory `json:"statusCategory" structs:"statusCategory"` } -// GetAllStatusesWithContext returns a list of all statuses associated with workflows. +// GetAllStatuses returns a list of all statuses associated with workflows. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-status-get -func (s *StatusService) GetAllStatusesWithContext(ctx context.Context) ([]Status, *Response, error) { +func (s *StatusService) GetAllStatuses(ctx context.Context) ([]Status, *Response, error) { apiEndpoint := "rest/api/2/status" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) @@ -38,8 +38,3 @@ func (s *StatusService) GetAllStatusesWithContext(ctx context.Context) ([]Status return statusList, resp, nil } - -// GetAllStatuses wraps GetAllStatusesWithContext using the background context. -func (s *StatusService) GetAllStatuses() ([]Status, *Response, error) { - return s.GetAllStatusesWithContext(context.Background()) -} diff --git a/onpremise/status_test.go b/onpremise/status_test.go index 0f9475f..a5a9a2f 100644 --- a/onpremise/status_test.go +++ b/onpremise/status_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "os" @@ -23,7 +24,7 @@ func TestStatusService_GetAllStatuses(t *testing.T) { fmt.Fprint(w, string(raw)) }) - statusList, _, err := testClient.Status.GetAllStatuses() + statusList, _, err := testClient.Status.GetAllStatuses(context.Background()) if statusList == nil { t.Error("Expected statusList. statusList is nill") From f15fe0d37d99072baadd7b6894a160c89c3d29d6 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 19:01:07 +0200 Subject: [PATCH 023/189] Sprint Service: Remove "WithContext" API methods --- cloud/sprint.go | 28 ++++++---------------------- cloud/sprint_test.go | 7 ++++--- onpremise/sprint.go | 28 ++++++---------------------- onpremise/sprint_test.go | 7 ++++--- 4 files changed, 20 insertions(+), 50 deletions(-) diff --git a/cloud/sprint.go b/cloud/sprint.go index e589159..1834067 100644 --- a/cloud/sprint.go +++ b/cloud/sprint.go @@ -21,13 +21,13 @@ type IssuesInSprintResult struct { Issues []Issue `json:"issues"` } -// MoveIssuesToSprintWithContext moves issues to a sprint, for a given sprint Id. +// MoveIssuesToSprint moves issues to a sprint, for a given sprint Id. // Issues can only be moved to open or active sprints. // The maximum number of issues that can be moved in one operation is 50. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-moveIssuesToSprint // Caller must close resp.Body -func (s *SprintService) MoveIssuesToSprintWithContext(ctx context.Context, sprintID int, issueIDs []string) (*Response, error) { +func (s *SprintService) MoveIssuesToSprint(ctx context.Context, sprintID int, issueIDs []string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) payload := IssuesWrapper{Issues: issueIDs} @@ -45,18 +45,12 @@ func (s *SprintService) MoveIssuesToSprintWithContext(ctx context.Context, sprin return resp, err } -// MoveIssuesToSprint wraps MoveIssuesToSprintWithContext using the background context. -// Caller must close resp.Body -func (s *SprintService) MoveIssuesToSprint(sprintID int, issueIDs []string) (*Response, error) { - return s.MoveIssuesToSprintWithContext(context.Background(), sprintID, issueIDs) -} - -// GetIssuesForSprintWithContext returns all issues in a sprint, for a given sprint Id. +// GetIssuesForSprint returns all issues in a sprint, for a given sprint Id. // This only includes issues that the user has permission to view. // By default, the returned issues are ordered by rank. // // Jira API Docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-getIssuesForSprint -func (s *SprintService) GetIssuesForSprintWithContext(ctx context.Context, sprintID int) ([]Issue, *Response, error) { +func (s *SprintService) GetIssuesForSprint(ctx context.Context, sprintID int) ([]Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) @@ -74,12 +68,7 @@ func (s *SprintService) GetIssuesForSprintWithContext(ctx context.Context, sprin return result.Issues, resp, err } -// GetIssuesForSprint wraps GetIssuesForSprintWithContext using the background context. -func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, error) { - return s.GetIssuesForSprintWithContext(context.Background(), sprintID) -} - -// GetIssueWithContext returns a full representation of the issue for the given issue key. +// GetIssue returns a full representation of the issue for the given issue key. // Jira will attempt to identify the issue by the issueIdOrKey path parameter. // This can be an issue id, or an issue key. // If the issue cannot be found via an exact match, Jira will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved. @@ -89,7 +78,7 @@ func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, er // Jira API docs: https://docs.atlassian.com/jira-software/REST/7.3.1/#agile/1.0/issue-getIssue // // TODO: create agile service for holding all agile apis' implementation -func (s *SprintService) GetIssueWithContext(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { +func (s *SprintService) GetIssue(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/issue/%s", issueID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) @@ -116,8 +105,3 @@ func (s *SprintService) GetIssueWithContext(ctx context.Context, issueID string, return issue, resp, nil } - -// GetIssue wraps GetIssueWithContext using the background context. -func (s *SprintService) GetIssue(issueID string, options *GetQueryOptions) (*Issue, *Response, error) { - return s.GetIssueWithContext(context.Background(), issueID, options) -} diff --git a/cloud/sprint_test.go b/cloud/sprint_test.go index 464d0c0..f12bd2b 100644 --- a/cloud/sprint_test.go +++ b/cloud/sprint_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "encoding/json" "fmt" "net/http" @@ -32,7 +33,7 @@ func TestSprintService_MoveIssuesToSprint(t *testing.T) { t.Errorf("Expected %s to be in payload, got %s instead", issuesToMove[0], payload.Issues[0]) } }) - _, err := testClient.Sprint.MoveIssuesToSprint(123, issuesToMove) + _, err := testClient.Sprint.MoveIssuesToSprint(context.Background(), 123, issuesToMove) if err != nil { t.Errorf("Got error: %v", err) @@ -54,7 +55,7 @@ func TestSprintService_GetIssuesForSprint(t *testing.T) { fmt.Fprint(w, string(raw)) }) - issues, _, err := testClient.Sprint.GetIssuesForSprint(123) + issues, _, err := testClient.Sprint.GetIssuesForSprint(context.Background(), 123) if err != nil { t.Errorf("Error given: %v", err) } @@ -79,7 +80,7 @@ func TestSprintService_GetIssue(t *testing.T) { fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"sprint": {"id": 37,"self": "http://www.example.com/jira/rest/agile/1.0/sprint/13", "state": "future", "name": "sprint 2"}, "epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) - issue, _, err := testClient.Sprint.GetIssue("10002", nil) + issue, _, err := testClient.Sprint.GetIssue(context.Background(), "10002", nil) if err != nil { t.Errorf("Error given: %s", err) } diff --git a/onpremise/sprint.go b/onpremise/sprint.go index 667df05..25011af 100644 --- a/onpremise/sprint.go +++ b/onpremise/sprint.go @@ -21,13 +21,13 @@ type IssuesInSprintResult struct { Issues []Issue `json:"issues"` } -// MoveIssuesToSprintWithContext moves issues to a sprint, for a given sprint Id. +// MoveIssuesToSprint moves issues to a sprint, for a given sprint Id. // Issues can only be moved to open or active sprints. // The maximum number of issues that can be moved in one operation is 50. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-moveIssuesToSprint // Caller must close resp.Body -func (s *SprintService) MoveIssuesToSprintWithContext(ctx context.Context, sprintID int, issueIDs []string) (*Response, error) { +func (s *SprintService) MoveIssuesToSprint(ctx context.Context, sprintID int, issueIDs []string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) payload := IssuesWrapper{Issues: issueIDs} @@ -45,18 +45,12 @@ func (s *SprintService) MoveIssuesToSprintWithContext(ctx context.Context, sprin return resp, err } -// MoveIssuesToSprint wraps MoveIssuesToSprintWithContext using the background context. -// Caller must close resp.Body -func (s *SprintService) MoveIssuesToSprint(sprintID int, issueIDs []string) (*Response, error) { - return s.MoveIssuesToSprintWithContext(context.Background(), sprintID, issueIDs) -} - -// GetIssuesForSprintWithContext returns all issues in a sprint, for a given sprint Id. +// GetIssuesForSprint returns all issues in a sprint, for a given sprint Id. // This only includes issues that the user has permission to view. // By default, the returned issues are ordered by rank. // // Jira API Docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-getIssuesForSprint -func (s *SprintService) GetIssuesForSprintWithContext(ctx context.Context, sprintID int) ([]Issue, *Response, error) { +func (s *SprintService) GetIssuesForSprint(ctx context.Context, sprintID int) ([]Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) @@ -74,12 +68,7 @@ func (s *SprintService) GetIssuesForSprintWithContext(ctx context.Context, sprin return result.Issues, resp, err } -// GetIssuesForSprint wraps GetIssuesForSprintWithContext using the background context. -func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, error) { - return s.GetIssuesForSprintWithContext(context.Background(), sprintID) -} - -// GetIssueWithContext returns a full representation of the issue for the given issue key. +// GetIssue returns a full representation of the issue for the given issue key. // Jira will attempt to identify the issue by the issueIdOrKey path parameter. // This can be an issue id, or an issue key. // If the issue cannot be found via an exact match, Jira will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved. @@ -89,7 +78,7 @@ func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, er // Jira API docs: https://docs.atlassian.com/jira-software/REST/7.3.1/#agile/1.0/issue-getIssue // // TODO: create agile service for holding all agile apis' implementation -func (s *SprintService) GetIssueWithContext(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { +func (s *SprintService) GetIssue(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/issue/%s", issueID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) @@ -116,8 +105,3 @@ func (s *SprintService) GetIssueWithContext(ctx context.Context, issueID string, return issue, resp, nil } - -// GetIssue wraps GetIssueWithContext using the background context. -func (s *SprintService) GetIssue(issueID string, options *GetQueryOptions) (*Issue, *Response, error) { - return s.GetIssueWithContext(context.Background(), issueID, options) -} diff --git a/onpremise/sprint_test.go b/onpremise/sprint_test.go index 25e743a..d063a69 100644 --- a/onpremise/sprint_test.go +++ b/onpremise/sprint_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "encoding/json" "fmt" "net/http" @@ -32,7 +33,7 @@ func TestSprintService_MoveIssuesToSprint(t *testing.T) { t.Errorf("Expected %s to be in payload, got %s instead", issuesToMove[0], payload.Issues[0]) } }) - _, err := testClient.Sprint.MoveIssuesToSprint(123, issuesToMove) + _, err := testClient.Sprint.MoveIssuesToSprint(context.Background(), 123, issuesToMove) if err != nil { t.Errorf("Got error: %v", err) @@ -54,7 +55,7 @@ func TestSprintService_GetIssuesForSprint(t *testing.T) { fmt.Fprint(w, string(raw)) }) - issues, _, err := testClient.Sprint.GetIssuesForSprint(123) + issues, _, err := testClient.Sprint.GetIssuesForSprint(context.Background(), 123) if err != nil { t.Errorf("Error given: %v", err) } @@ -79,7 +80,7 @@ func TestSprintService_GetIssue(t *testing.T) { fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"sprint": {"id": 37,"self": "http://www.example.com/jira/rest/agile/1.0/sprint/13", "state": "future", "name": "sprint 2"}, "epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) - issue, _, err := testClient.Sprint.GetIssue("10002", nil) + issue, _, err := testClient.Sprint.GetIssue(context.Background(), "10002", nil) if err != nil { t.Errorf("Error given: %s", err) } From 90390b6b71cb73873c8c5c402b039f7cbf4770a1 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 19:04:33 +0200 Subject: [PATCH 024/189] ServiceDesk Service: Remove "WithContext" API methods --- cloud/servicedesk.go | 56 ++++++++--------------------------- cloud/servicedesk_test.go | 19 ++++++------ onpremise/servicedesk.go | 56 ++++++++--------------------------- onpremise/servicedesk_test.go | 19 ++++++------ 4 files changed, 44 insertions(+), 106 deletions(-) diff --git a/cloud/servicedesk.go b/cloud/servicedesk.go index 69df04b..0b9282d 100644 --- a/cloud/servicedesk.go +++ b/cloud/servicedesk.go @@ -17,11 +17,11 @@ type ServiceDeskOrganizationDTO struct { OrganizationID int `json:"organizationId,omitempty" structs:"organizationId,omitempty"` } -// GetOrganizationsWithContext returns a list of +// GetOrganizations returns a list of // all organizations associated with a service desk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-get -func (s *ServiceDeskService) GetOrganizationsWithContext(ctx context.Context, serviceDeskID interface{}, start int, limit int, accountID string) (*PagedDTO, *Response, error) { +func (s *ServiceDeskService) GetOrganizations(ctx context.Context, serviceDeskID interface{}, start int, limit int, accountID string) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization?start=%d&limit=%d", serviceDeskID, start, limit) if accountID != "" { apiEndPoint += fmt.Sprintf("&accountId=%s", accountID) @@ -44,19 +44,14 @@ func (s *ServiceDeskService) GetOrganizationsWithContext(ctx context.Context, se return orgs, resp, nil } -// GetOrganizations wraps GetOrganizationsWithContext using the background context. -func (s *ServiceDeskService) GetOrganizations(serviceDeskID interface{}, start int, limit int, accountID string) (*PagedDTO, *Response, error) { - return s.GetOrganizationsWithContext(context.Background(), serviceDeskID, start, limit, accountID) -} - -// AddOrganizationWithContext adds an organization to +// AddOrganization adds an organization to // a service desk. If the organization ID is already // associated with the service desk, no change is made // and the resource returns a 204 success code. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-post // Caller must close resp.Body -func (s *ServiceDeskService) AddOrganizationWithContext(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) { +func (s *ServiceDeskService) AddOrganization(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization", serviceDeskID) organization := ServiceDeskOrganizationDTO{ @@ -78,20 +73,14 @@ func (s *ServiceDeskService) AddOrganizationWithContext(ctx context.Context, ser return resp, nil } -// AddOrganization wraps AddOrganizationWithContext using the background context. -// Caller must close resp.Body -func (s *ServiceDeskService) AddOrganization(serviceDeskID interface{}, organizationID int) (*Response, error) { - return s.AddOrganizationWithContext(context.Background(), serviceDeskID, organizationID) -} - -// RemoveOrganizationWithContext removes an organization +// RemoveOrganization removes an organization // from a service desk. If the organization ID does not // match an organization associated with the service desk, // no change is made and the resource returns a 204 success code. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-delete // Caller must close resp.Body -func (s *ServiceDeskService) RemoveOrganizationWithContext(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) { +func (s *ServiceDeskService) RemoveOrganization(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization", serviceDeskID) organization := ServiceDeskOrganizationDTO{ @@ -113,16 +102,10 @@ func (s *ServiceDeskService) RemoveOrganizationWithContext(ctx context.Context, return resp, nil } -// RemoveOrganization wraps RemoveOrganizationWithContext using the background context. -// Caller must close resp.Body -func (s *ServiceDeskService) RemoveOrganization(serviceDeskID interface{}, organizationID int) (*Response, error) { - return s.RemoveOrganizationWithContext(context.Background(), serviceDeskID, organizationID) -} - -// AddCustomersWithContext adds customers to the given service desk. +// AddCustomers adds customers to the given service desk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-post -func (s *ServiceDeskService) AddCustomersWithContext(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) { +func (s *ServiceDeskService) AddCustomers(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) payload := struct { @@ -146,15 +129,10 @@ func (s *ServiceDeskService) AddCustomersWithContext(ctx context.Context, servic return resp, nil } -// AddCustomers wraps AddCustomersWithContext using the background context. -func (s *ServiceDeskService) AddCustomers(serviceDeskID interface{}, acountIDs ...string) (*Response, error) { - return s.AddCustomersWithContext(context.Background(), serviceDeskID, acountIDs...) -} - -// RemoveCustomersWithContext removes customers to the given service desk. +// RemoveCustomers removes customers to the given service desk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-delete -func (s *ServiceDeskService) RemoveCustomersWithContext(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) { +func (s *ServiceDeskService) RemoveCustomers(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) payload := struct { @@ -178,15 +156,10 @@ func (s *ServiceDeskService) RemoveCustomersWithContext(ctx context.Context, ser return resp, nil } -// RemoveCustomers wraps RemoveCustomersWithContext using the background context. -func (s *ServiceDeskService) RemoveCustomers(serviceDeskID interface{}, acountIDs ...string) (*Response, error) { - return s.RemoveCustomersWithContext(context.Background(), serviceDeskID, acountIDs...) -} - -// ListCustomersWithContext lists customers for a ServiceDesk. +// ListCustomers lists customers for a ServiceDesk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-get -func (s *ServiceDeskService) ListCustomersWithContext(ctx context.Context, serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) { +func (s *ServiceDeskService) ListCustomers(ctx context.Context, serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -217,8 +190,3 @@ func (s *ServiceDeskService) ListCustomersWithContext(ctx context.Context, servi return customerList, resp, nil } - -// ListCustomers wraps ListCustomersWithContext using the background context. -func (s *ServiceDeskService) ListCustomers(serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) { - return s.ListCustomersWithContext(context.Background(), serviceDeskID, options) -} diff --git a/cloud/servicedesk_test.go b/cloud/servicedesk_test.go index e552705..a982cdb 100644 --- a/cloud/servicedesk_test.go +++ b/cloud/servicedesk_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "encoding/json" "fmt" "net/http" @@ -56,7 +57,7 @@ func TestServiceDeskService_GetOrganizations(t *testing.T) { }`) }) - orgs, _, err := testClient.ServiceDesk.GetOrganizations(10001, 3, 3, "") + orgs, _, err := testClient.ServiceDesk.GetOrganizations(context.Background(), 10001, 3, 3, "") if orgs == nil { t.Error("Expected Organizations. Result is nil") @@ -79,7 +80,7 @@ func TestServiceDeskService_AddOrganizations(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - _, err := testClient.ServiceDesk.AddOrganization(10001, 1) + _, err := testClient.ServiceDesk.AddOrganization(context.Background(), 10001, 1) if err != nil { t.Errorf("Error given: %s", err) @@ -96,7 +97,7 @@ func TestServiceDeskService_RemoveOrganizations(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - _, err := testClient.ServiceDesk.RemoveOrganization(10001, 1) + _, err := testClient.ServiceDesk.RemoveOrganization(context.Background(), 10001, 1) if err != nil { t.Errorf("Error given: %s", err) @@ -149,7 +150,7 @@ func TestServiceDeskServiceStringServiceDeskID_GetOrganizations(t *testing.T) { }`) }) - orgs, _, err := testClient.ServiceDesk.GetOrganizations("TEST", 3, 3, "") + orgs, _, err := testClient.ServiceDesk.GetOrganizations(context.Background(), "TEST", 3, 3, "") if orgs == nil { t.Error("Expected Organizations. Result is nil") @@ -172,7 +173,7 @@ func TestServiceDeskServiceStringServiceDeskID_AddOrganizations(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - _, err := testClient.ServiceDesk.AddOrganization("TEST", 1) + _, err := testClient.ServiceDesk.AddOrganization(context.Background(), "TEST", 1) if err != nil { t.Errorf("Error given: %s", err) @@ -189,7 +190,7 @@ func TestServiceDeskServiceStringServiceDeskID_RemoveOrganizations(t *testing.T) w.WriteHeader(http.StatusNoContent) }) - _, err := testClient.ServiceDesk.RemoveOrganization("TEST", 1) + _, err := testClient.ServiceDesk.RemoveOrganization(context.Background(), "TEST", 1) if err != nil { t.Errorf("Error given: %s", err) @@ -242,7 +243,7 @@ func TestServiceDeskService_AddCustomers(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - _, err := testClient.ServiceDesk.AddCustomers(test.serviceDeskID, wantAccountIDs...) + _, err := testClient.ServiceDesk.AddCustomers(context.Background(), test.serviceDeskID, wantAccountIDs...) if err != nil { t.Errorf("Error given: %s", err) @@ -308,7 +309,7 @@ func TestServiceDeskService_RemoveCustomers(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - _, err := testClient.ServiceDesk.RemoveCustomers(test.serviceDeskID, wantAccountIDs...) + _, err := testClient.ServiceDesk.RemoveCustomers(context.Background(), test.serviceDeskID, wantAccountIDs...) if err != nil { t.Errorf("Error given: %s", err) @@ -409,7 +410,7 @@ func TestServiceDeskService_ListCustomers(t *testing.T) { }`)) }) - customerList, _, err := testClient.ServiceDesk.ListCustomers(test.serviceDeskID, wantOptions) + customerList, _, err := testClient.ServiceDesk.ListCustomers(context.Background(), test.serviceDeskID, wantOptions) if err != nil { t.Fatal(err) } diff --git a/onpremise/servicedesk.go b/onpremise/servicedesk.go index c303904..d1c86ac 100644 --- a/onpremise/servicedesk.go +++ b/onpremise/servicedesk.go @@ -17,11 +17,11 @@ type ServiceDeskOrganizationDTO struct { OrganizationID int `json:"organizationId,omitempty" structs:"organizationId,omitempty"` } -// GetOrganizationsWithContext returns a list of +// GetOrganizations returns a list of // all organizations associated with a service desk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-get -func (s *ServiceDeskService) GetOrganizationsWithContext(ctx context.Context, serviceDeskID interface{}, start int, limit int, accountID string) (*PagedDTO, *Response, error) { +func (s *ServiceDeskService) GetOrganizations(ctx context.Context, serviceDeskID interface{}, start int, limit int, accountID string) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization?start=%d&limit=%d", serviceDeskID, start, limit) if accountID != "" { apiEndPoint += fmt.Sprintf("&accountId=%s", accountID) @@ -44,19 +44,14 @@ func (s *ServiceDeskService) GetOrganizationsWithContext(ctx context.Context, se return orgs, resp, nil } -// GetOrganizations wraps GetOrganizationsWithContext using the background context. -func (s *ServiceDeskService) GetOrganizations(serviceDeskID interface{}, start int, limit int, accountID string) (*PagedDTO, *Response, error) { - return s.GetOrganizationsWithContext(context.Background(), serviceDeskID, start, limit, accountID) -} - -// AddOrganizationWithContext adds an organization to +// AddOrganization adds an organization to // a service desk. If the organization ID is already // associated with the service desk, no change is made // and the resource returns a 204 success code. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-post // Caller must close resp.Body -func (s *ServiceDeskService) AddOrganizationWithContext(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) { +func (s *ServiceDeskService) AddOrganization(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization", serviceDeskID) organization := ServiceDeskOrganizationDTO{ @@ -78,20 +73,14 @@ func (s *ServiceDeskService) AddOrganizationWithContext(ctx context.Context, ser return resp, nil } -// AddOrganization wraps AddOrganizationWithContext using the background context. -// Caller must close resp.Body -func (s *ServiceDeskService) AddOrganization(serviceDeskID interface{}, organizationID int) (*Response, error) { - return s.AddOrganizationWithContext(context.Background(), serviceDeskID, organizationID) -} - -// RemoveOrganizationWithContext removes an organization +// RemoveOrganization removes an organization // from a service desk. If the organization ID does not // match an organization associated with the service desk, // no change is made and the resource returns a 204 success code. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-delete // Caller must close resp.Body -func (s *ServiceDeskService) RemoveOrganizationWithContext(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) { +func (s *ServiceDeskService) RemoveOrganization(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization", serviceDeskID) organization := ServiceDeskOrganizationDTO{ @@ -113,16 +102,10 @@ func (s *ServiceDeskService) RemoveOrganizationWithContext(ctx context.Context, return resp, nil } -// RemoveOrganization wraps RemoveOrganizationWithContext using the background context. -// Caller must close resp.Body -func (s *ServiceDeskService) RemoveOrganization(serviceDeskID interface{}, organizationID int) (*Response, error) { - return s.RemoveOrganizationWithContext(context.Background(), serviceDeskID, organizationID) -} - -// AddCustomersWithContext adds customers to the given service desk. +// AddCustomers adds customers to the given service desk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-post -func (s *ServiceDeskService) AddCustomersWithContext(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) { +func (s *ServiceDeskService) AddCustomers(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) payload := struct { @@ -146,15 +129,10 @@ func (s *ServiceDeskService) AddCustomersWithContext(ctx context.Context, servic return resp, nil } -// AddCustomers wraps AddCustomersWithContext using the background context. -func (s *ServiceDeskService) AddCustomers(serviceDeskID interface{}, acountIDs ...string) (*Response, error) { - return s.AddCustomersWithContext(context.Background(), serviceDeskID, acountIDs...) -} - -// RemoveCustomersWithContext removes customers to the given service desk. +// RemoveCustomers removes customers to the given service desk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-delete -func (s *ServiceDeskService) RemoveCustomersWithContext(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) { +func (s *ServiceDeskService) RemoveCustomers(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) payload := struct { @@ -178,15 +156,10 @@ func (s *ServiceDeskService) RemoveCustomersWithContext(ctx context.Context, ser return resp, nil } -// RemoveCustomers wraps RemoveCustomersWithContext using the background context. -func (s *ServiceDeskService) RemoveCustomers(serviceDeskID interface{}, acountIDs ...string) (*Response, error) { - return s.RemoveCustomersWithContext(context.Background(), serviceDeskID, acountIDs...) -} - -// ListCustomersWithContext lists customers for a ServiceDesk. +// ListCustomers lists customers for a ServiceDesk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-get -func (s *ServiceDeskService) ListCustomersWithContext(ctx context.Context, serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) { +func (s *ServiceDeskService) ListCustomers(ctx context.Context, serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -217,8 +190,3 @@ func (s *ServiceDeskService) ListCustomersWithContext(ctx context.Context, servi return customerList, resp, nil } - -// ListCustomers wraps ListCustomersWithContext using the background context. -func (s *ServiceDeskService) ListCustomers(serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) { - return s.ListCustomersWithContext(context.Background(), serviceDeskID, options) -} diff --git a/onpremise/servicedesk_test.go b/onpremise/servicedesk_test.go index 5828e12..532a477 100644 --- a/onpremise/servicedesk_test.go +++ b/onpremise/servicedesk_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "encoding/json" "fmt" "net/http" @@ -56,7 +57,7 @@ func TestServiceDeskService_GetOrganizations(t *testing.T) { }`) }) - orgs, _, err := testClient.ServiceDesk.GetOrganizations(10001, 3, 3, "") + orgs, _, err := testClient.ServiceDesk.GetOrganizations(context.Background(), 10001, 3, 3, "") if orgs == nil { t.Error("Expected Organizations. Result is nil") @@ -79,7 +80,7 @@ func TestServiceDeskService_AddOrganizations(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - _, err := testClient.ServiceDesk.AddOrganization(10001, 1) + _, err := testClient.ServiceDesk.AddOrganization(context.Background(), 10001, 1) if err != nil { t.Errorf("Error given: %s", err) @@ -96,7 +97,7 @@ func TestServiceDeskService_RemoveOrganizations(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - _, err := testClient.ServiceDesk.RemoveOrganization(10001, 1) + _, err := testClient.ServiceDesk.RemoveOrganization(context.Background(), 10001, 1) if err != nil { t.Errorf("Error given: %s", err) @@ -149,7 +150,7 @@ func TestServiceDeskServiceStringServiceDeskID_GetOrganizations(t *testing.T) { }`) }) - orgs, _, err := testClient.ServiceDesk.GetOrganizations("TEST", 3, 3, "") + orgs, _, err := testClient.ServiceDesk.GetOrganizations(context.Background(), "TEST", 3, 3, "") if orgs == nil { t.Error("Expected Organizations. Result is nil") @@ -172,7 +173,7 @@ func TestServiceDeskServiceStringServiceDeskID_AddOrganizations(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - _, err := testClient.ServiceDesk.AddOrganization("TEST", 1) + _, err := testClient.ServiceDesk.AddOrganization(context.Background(), "TEST", 1) if err != nil { t.Errorf("Error given: %s", err) @@ -189,7 +190,7 @@ func TestServiceDeskServiceStringServiceDeskID_RemoveOrganizations(t *testing.T) w.WriteHeader(http.StatusNoContent) }) - _, err := testClient.ServiceDesk.RemoveOrganization("TEST", 1) + _, err := testClient.ServiceDesk.RemoveOrganization(context.Background(), "TEST", 1) if err != nil { t.Errorf("Error given: %s", err) @@ -242,7 +243,7 @@ func TestServiceDeskService_AddCustomers(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - _, err := testClient.ServiceDesk.AddCustomers(test.serviceDeskID, wantAccountIDs...) + _, err := testClient.ServiceDesk.AddCustomers(context.Background(), test.serviceDeskID, wantAccountIDs...) if err != nil { t.Errorf("Error given: %s", err) @@ -308,7 +309,7 @@ func TestServiceDeskService_RemoveCustomers(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - _, err := testClient.ServiceDesk.RemoveCustomers(test.serviceDeskID, wantAccountIDs...) + _, err := testClient.ServiceDesk.RemoveCustomers(context.Background(), test.serviceDeskID, wantAccountIDs...) if err != nil { t.Errorf("Error given: %s", err) @@ -409,7 +410,7 @@ func TestServiceDeskService_ListCustomers(t *testing.T) { }`)) }) - customerList, _, err := testClient.ServiceDesk.ListCustomers(test.serviceDeskID, wantOptions) + customerList, _, err := testClient.ServiceDesk.ListCustomers(context.Background(), test.serviceDeskID, wantOptions) if err != nil { t.Fatal(err) } From ccffe0a5027a92181b8e693738a9a176232f484e Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 19:06:32 +0200 Subject: [PATCH 025/189] Role Service: Remove "WithContext" API methods --- cloud/role.go | 18 ++++-------------- cloud/role_test.go | 9 +++++---- onpremise/role.go | 18 ++++-------------- onpremise/role_test.go | 9 +++++---- 4 files changed, 18 insertions(+), 36 deletions(-) diff --git a/cloud/role.go b/cloud/role.go index 7854bf3..ed918eb 100644 --- a/cloud/role.go +++ b/cloud/role.go @@ -34,10 +34,10 @@ type ActorUser struct { AccountID string `json:"accountId" structs:"accountId"` } -// GetListWithContext returns a list of all available project roles +// GetList returns a list of all available project roles // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-get -func (s *RoleService) GetListWithContext(ctx context.Context) (*[]Role, *Response, error) { +func (s *RoleService) GetList(ctx context.Context) (*[]Role, *Response, error) { apiEndpoint := "rest/api/3/role" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -52,15 +52,10 @@ func (s *RoleService) GetListWithContext(ctx context.Context) (*[]Role, *Respons return roles, resp, err } -// GetList wraps GetListWithContext using the background context. -func (s *RoleService) GetList() (*[]Role, *Response, error) { - return s.GetListWithContext(context.Background()) -} - -// GetWithContext retreives a single Role from Jira +// Get retreives a single Role from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-id-get -func (s *RoleService) GetWithContext(ctx context.Context, roleID int) (*Role, *Response, error) { +func (s *RoleService) Get(ctx context.Context, roleID int) (*Role, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/3/role/%d", roleID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -78,8 +73,3 @@ func (s *RoleService) GetWithContext(ctx context.Context, roleID int) (*Role, *R return role, resp, err } - -// Get wraps GetWithContext using the background context. -func (s *RoleService) Get(roleID int) (*Role, *Response, error) { - return s.GetWithContext(context.Background(), roleID) -} diff --git a/cloud/role_test.go b/cloud/role_test.go index 88ca0af..a4c7a91 100644 --- a/cloud/role_test.go +++ b/cloud/role_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "os" @@ -23,7 +24,7 @@ func TestRoleService_GetList_NoList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - roles, _, err := testClient.Role.GetList() + roles, _, err := testClient.Role.GetList(context.Background()) if roles != nil { t.Errorf("Expected role list has %d entries but should be nil", len(*roles)) } @@ -47,7 +48,7 @@ func TestRoleService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - roles, _, err := testClient.Role.GetList() + roles, _, err := testClient.Role.GetList(context.Background()) if err != nil { t.Errorf("Error given: %v", err) } @@ -74,7 +75,7 @@ func TestRoleService_Get_NoRole(t *testing.T) { fmt.Fprint(writer, string(raw)) }) - role, _, err := testClient.Role.Get(99999) + role, _, err := testClient.Role.Get(context.Background(), 99999) if role != nil { t.Errorf("Expected nil, got role %v", role) } @@ -97,7 +98,7 @@ func TestRoleService_Get(t *testing.T) { fmt.Fprint(writer, string(raw)) }) - role, _, err := testClient.Role.Get(10002) + role, _, err := testClient.Role.Get(context.Background(), 10002) if role == nil { t.Errorf("Expected Role, got nil") } diff --git a/onpremise/role.go b/onpremise/role.go index f3b9f72..b72c0c7 100644 --- a/onpremise/role.go +++ b/onpremise/role.go @@ -34,10 +34,10 @@ type ActorUser struct { AccountID string `json:"accountId" structs:"accountId"` } -// GetListWithContext returns a list of all available project roles +// GetList returns a list of all available project roles // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-get -func (s *RoleService) GetListWithContext(ctx context.Context) (*[]Role, *Response, error) { +func (s *RoleService) GetList(ctx context.Context) (*[]Role, *Response, error) { apiEndpoint := "rest/api/3/role" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -52,15 +52,10 @@ func (s *RoleService) GetListWithContext(ctx context.Context) (*[]Role, *Respons return roles, resp, err } -// GetList wraps GetListWithContext using the background context. -func (s *RoleService) GetList() (*[]Role, *Response, error) { - return s.GetListWithContext(context.Background()) -} - -// GetWithContext retreives a single Role from Jira +// Get retreives a single Role from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-id-get -func (s *RoleService) GetWithContext(ctx context.Context, roleID int) (*Role, *Response, error) { +func (s *RoleService) Get(ctx context.Context, roleID int) (*Role, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/3/role/%d", roleID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -78,8 +73,3 @@ func (s *RoleService) GetWithContext(ctx context.Context, roleID int) (*Role, *R return role, resp, err } - -// Get wraps GetWithContext using the background context. -func (s *RoleService) Get(roleID int) (*Role, *Response, error) { - return s.GetWithContext(context.Background(), roleID) -} diff --git a/onpremise/role_test.go b/onpremise/role_test.go index 524e9c3..fba7bab 100644 --- a/onpremise/role_test.go +++ b/onpremise/role_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "os" @@ -23,7 +24,7 @@ func TestRoleService_GetList_NoList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - roles, _, err := testClient.Role.GetList() + roles, _, err := testClient.Role.GetList(context.Background()) if roles != nil { t.Errorf("Expected role list has %d entries but should be nil", len(*roles)) } @@ -47,7 +48,7 @@ func TestRoleService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - roles, _, err := testClient.Role.GetList() + roles, _, err := testClient.Role.GetList(context.Background()) if err != nil { t.Errorf("Error given: %v", err) } @@ -74,7 +75,7 @@ func TestRoleService_Get_NoRole(t *testing.T) { fmt.Fprint(writer, string(raw)) }) - role, _, err := testClient.Role.Get(99999) + role, _, err := testClient.Role.Get(context.Background(), 99999) if role != nil { t.Errorf("Expected nil, got role %v", role) } @@ -97,7 +98,7 @@ func TestRoleService_Get(t *testing.T) { fmt.Fprint(writer, string(raw)) }) - role, _, err := testClient.Role.Get(10002) + role, _, err := testClient.Role.Get(context.Background(), 10002) if role == nil { t.Errorf("Expected Role, got nil") } From a4e7a7263a4f2d66575852fb915ecf75a89c0691 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 19:14:43 +0200 Subject: [PATCH 026/189] Request Service: Remove "WithContext" API methods --- cloud/request.go | 18 ++++-------------- cloud/request_test.go | 5 +++-- onpremise/request.go | 18 ++++-------------- onpremise/request_test.go | 5 +++-- 4 files changed, 14 insertions(+), 32 deletions(-) diff --git a/cloud/request.go b/cloud/request.go index 6d276ee..286b223 100644 --- a/cloud/request.go +++ b/cloud/request.go @@ -54,10 +54,10 @@ type RequestComment struct { Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"` } -// CreateWithContext creates a new request. +// Create creates a new request. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-post -func (r *RequestService) CreateWithContext(ctx context.Context, requester string, participants []string, request *Request) (*Request, *Response, error) { +func (r *RequestService) Create(ctx context.Context, requester string, participants []string, request *Request) (*Request, *Response, error) { apiEndpoint := "rest/servicedeskapi/request" payload := struct { @@ -90,15 +90,10 @@ func (r *RequestService) CreateWithContext(ctx context.Context, requester string return responseRequest, resp, nil } -// Create wraps CreateWithContext using the background context. -func (r *RequestService) Create(requester string, participants []string, request *Request) (*Request, *Response, error) { - return r.CreateWithContext(context.Background(), requester, participants, request) -} - -// CreateCommentWithContext creates a comment on a request. +// CreateComment creates a comment on a request. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-comment-post -func (r *RequestService) CreateCommentWithContext(ctx context.Context, issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) { +func (r *RequestService) CreateComment(ctx context.Context, issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/request/%v/comment", issueIDOrKey) req, err := r.client.NewRequest(ctx, "POST", apiEndpoint, comment) @@ -114,8 +109,3 @@ func (r *RequestService) CreateCommentWithContext(ctx context.Context, issueIDOr return responseComment, resp, nil } - -// CreateComment wraps CreateCommentWithContext using the background context. -func (r *RequestService) CreateComment(issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) { - return r.CreateCommentWithContext(context.Background(), issueIDOrKey, comment) -} diff --git a/cloud/request_test.go b/cloud/request_test.go index 4585a65..bc7d7bd 100644 --- a/cloud/request_test.go +++ b/cloud/request_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "encoding/json" "net/http" "reflect" @@ -126,7 +127,7 @@ func TestRequestService_Create(t *testing.T) { }, } - _, _, err := testClient.Request.Create(wantRequester, wantParticipants, request) + _, _, err := testClient.Request.Create(context.Background(), wantRequester, wantParticipants, request) if err != nil { t.Fatal(err) } @@ -192,7 +193,7 @@ func TestRequestService_CreateComment(t *testing.T) { Public: true, } - _, _, err := testClient.Request.CreateComment("HELPDESK-1", comment) + _, _, err := testClient.Request.CreateComment(context.Background(), "HELPDESK-1", comment) if err != nil { t.Fatal(err) } diff --git a/onpremise/request.go b/onpremise/request.go index b096005..1514979 100644 --- a/onpremise/request.go +++ b/onpremise/request.go @@ -54,10 +54,10 @@ type RequestComment struct { Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"` } -// CreateWithContext creates a new request. +// Create creates a new request. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-post -func (r *RequestService) CreateWithContext(ctx context.Context, requester string, participants []string, request *Request) (*Request, *Response, error) { +func (r *RequestService) Create(ctx context.Context, requester string, participants []string, request *Request) (*Request, *Response, error) { apiEndpoint := "rest/servicedeskapi/request" payload := struct { @@ -90,15 +90,10 @@ func (r *RequestService) CreateWithContext(ctx context.Context, requester string return responseRequest, resp, nil } -// Create wraps CreateWithContext using the background context. -func (r *RequestService) Create(requester string, participants []string, request *Request) (*Request, *Response, error) { - return r.CreateWithContext(context.Background(), requester, participants, request) -} - -// CreateCommentWithContext creates a comment on a request. +// CreateComment creates a comment on a request. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-comment-post -func (r *RequestService) CreateCommentWithContext(ctx context.Context, issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) { +func (r *RequestService) CreateComment(ctx context.Context, issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/request/%v/comment", issueIDOrKey) req, err := r.client.NewRequest(ctx, "POST", apiEndpoint, comment) @@ -114,8 +109,3 @@ func (r *RequestService) CreateCommentWithContext(ctx context.Context, issueIDOr return responseComment, resp, nil } - -// CreateComment wraps CreateCommentWithContext using the background context. -func (r *RequestService) CreateComment(issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) { - return r.CreateCommentWithContext(context.Background(), issueIDOrKey, comment) -} diff --git a/onpremise/request_test.go b/onpremise/request_test.go index 886f7ad..fcee64e 100644 --- a/onpremise/request_test.go +++ b/onpremise/request_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "encoding/json" "net/http" "reflect" @@ -126,7 +127,7 @@ func TestRequestService_Create(t *testing.T) { }, } - _, _, err := testClient.Request.Create(wantRequester, wantParticipants, request) + _, _, err := testClient.Request.Create(context.Background(), wantRequester, wantParticipants, request) if err != nil { t.Fatal(err) } @@ -192,7 +193,7 @@ func TestRequestService_CreateComment(t *testing.T) { Public: true, } - _, _, err := testClient.Request.CreateComment("HELPDESK-1", comment) + _, _, err := testClient.Request.CreateComment(context.Background(), "HELPDESK-1", comment) if err != nil { t.Fatal(err) } From 8e3f262d6ead0bd6995efdbb94b0271e978ab593 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 19:14:56 +0200 Subject: [PATCH 027/189] Resolution Service: Remove "WithContext" API methods --- cloud/resolution.go | 9 ++------- cloud/resolution_test.go | 3 ++- onpremise/resolution.go | 9 ++------- onpremise/resolution_test.go | 3 ++- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/cloud/resolution.go b/cloud/resolution.go index b10e44d..e08c46d 100644 --- a/cloud/resolution.go +++ b/cloud/resolution.go @@ -16,10 +16,10 @@ type Resolution struct { Name string `json:"name" structs:"name"` } -// GetListWithContext gets all resolutions from Jira +// GetList gets all resolutions from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-resolution-get -func (s *ResolutionService) GetListWithContext(ctx context.Context) ([]Resolution, *Response, error) { +func (s *ResolutionService) GetList(ctx context.Context) ([]Resolution, *Response, error) { apiEndpoint := "rest/api/2/resolution" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -33,8 +33,3 @@ func (s *ResolutionService) GetListWithContext(ctx context.Context) ([]Resolutio } return resolutionList, resp, nil } - -// GetList wraps GetListWithContext using the background context. -func (s *ResolutionService) GetList() ([]Resolution, *Response, error) { - return s.GetListWithContext(context.Background()) -} diff --git a/cloud/resolution_test.go b/cloud/resolution_test.go index 2ee0487..adcad66 100644 --- a/cloud/resolution_test.go +++ b/cloud/resolution_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestResolutionService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - resolution, _, err := testClient.Resolution.GetList() + resolution, _, err := testClient.Resolution.GetList(context.Background()) if resolution == nil { t.Error("Expected resolution list. Resolution list is nil") } diff --git a/onpremise/resolution.go b/onpremise/resolution.go index c2613fb..c7f4859 100644 --- a/onpremise/resolution.go +++ b/onpremise/resolution.go @@ -16,10 +16,10 @@ type Resolution struct { Name string `json:"name" structs:"name"` } -// GetListWithContext gets all resolutions from Jira +// GetList gets all resolutions from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-resolution-get -func (s *ResolutionService) GetListWithContext(ctx context.Context) ([]Resolution, *Response, error) { +func (s *ResolutionService) GetList(ctx context.Context) ([]Resolution, *Response, error) { apiEndpoint := "rest/api/2/resolution" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -33,8 +33,3 @@ func (s *ResolutionService) GetListWithContext(ctx context.Context) ([]Resolutio } return resolutionList, resp, nil } - -// GetList wraps GetListWithContext using the background context. -func (s *ResolutionService) GetList() ([]Resolution, *Response, error) { - return s.GetListWithContext(context.Background()) -} diff --git a/onpremise/resolution_test.go b/onpremise/resolution_test.go index 70629ca..ebaa0ef 100644 --- a/onpremise/resolution_test.go +++ b/onpremise/resolution_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestResolutionService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - resolution, _, err := testClient.Resolution.GetList() + resolution, _, err := testClient.Resolution.GetList(context.Background()) if resolution == nil { t.Error("Expected resolution list. Resolution list is nil") } From 18ac2bc682fff316dec9a522c9cac5b8a09c0a44 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 20:24:20 +0200 Subject: [PATCH 028/189] Metaissue Service: Remove "WithContext" API methods --- cloud/metaissue.go | 29 +++++++---------------------- cloud/metaissue_test.go | 9 +++++---- onpremise/metaissue.go | 29 +++++++---------------------- onpremise/metaissue_test.go | 9 +++++---- 4 files changed, 24 insertions(+), 52 deletions(-) diff --git a/cloud/metaissue.go b/cloud/metaissue.go index 438a5f3..dde3c83 100644 --- a/cloud/metaissue.go +++ b/cloud/metaissue.go @@ -48,18 +48,13 @@ type MetaIssueType struct { Fields tcontainer.MarshalMap `json:"fields,omitempty"` } -// GetCreateMetaWithContext makes the api call to get the meta information required to create a ticket -func (s *IssueService) GetCreateMetaWithContext(ctx context.Context, projectkeys string) (*CreateMetaInfo, *Response, error) { - return s.GetCreateMetaWithOptionsWithContext(ctx, &GetQueryOptions{ProjectKeys: projectkeys, Expand: "projects.issuetypes.fields"}) +// GetCreateMeta makes the api call to get the meta information required to create a ticket +func (s *IssueService) GetCreateMeta(ctx context.Context, projectkeys string) (*CreateMetaInfo, *Response, error) { + return s.GetCreateMetaWithOptions(ctx, &GetQueryOptions{ProjectKeys: projectkeys, Expand: "projects.issuetypes.fields"}) } -// GetCreateMeta wraps GetCreateMetaWithContext using the background context. -func (s *IssueService) GetCreateMeta(projectkeys string) (*CreateMetaInfo, *Response, error) { - return s.GetCreateMetaWithContext(context.Background(), projectkeys) -} - -// GetCreateMetaWithOptionsWithContext makes the api call to get the meta information without requiring to have a projectKey -func (s *IssueService) GetCreateMetaWithOptionsWithContext(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { +// GetCreateMetaWithOptions makes the api call to get the meta information without requiring to have a projectKey +func (s *IssueService) GetCreateMetaWithOptions(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { apiEndpoint := "rest/api/2/issue/createmeta" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) @@ -84,13 +79,8 @@ func (s *IssueService) GetCreateMetaWithOptionsWithContext(ctx context.Context, return meta, resp, nil } -// GetCreateMetaWithOptions wraps GetCreateMetaWithOptionsWithContext using the background context. -func (s *IssueService) GetCreateMetaWithOptions(options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { - return s.GetCreateMetaWithOptionsWithContext(context.Background(), options) -} - -// GetEditMetaWithContext makes the api call to get the edit meta information for an issue -func (s *IssueService) GetEditMetaWithContext(ctx context.Context, issue *Issue) (*EditMetaInfo, *Response, error) { +// GetEditMeta makes the api call to get the edit meta information for an issue +func (s *IssueService) GetEditMeta(ctx context.Context, issue *Issue) (*EditMetaInfo, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/issue/%s/editmeta", issue.Key) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) @@ -108,11 +98,6 @@ func (s *IssueService) GetEditMetaWithContext(ctx context.Context, issue *Issue) return meta, resp, nil } -// GetEditMeta wraps GetEditMetaWithContext using the background context. -func (s *IssueService) GetEditMeta(issue *Issue) (*EditMetaInfo, *Response, error) { - return s.GetEditMetaWithContext(context.Background(), issue) -} - // GetProjectWithName returns a project with "name" from the meta information received. If not found, this returns nil. // The comparison of the name is case insensitive. func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject { diff --git a/cloud/metaissue_test.go b/cloud/metaissue_test.go index 15ff751..ef720e9 100644 --- a/cloud/metaissue_test.go +++ b/cloud/metaissue_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "net/url" @@ -348,7 +349,7 @@ func TestIssueService_GetCreateMeta_Success(t *testing.T) { }`) }) - issue, _, err := testClient.Issue.GetCreateMeta("SPN") + issue, _, err := testClient.Issue.GetCreateMeta(context.Background(), "SPN") if err != nil { t.Errorf("Expected nil error but got %s", err) } @@ -420,7 +421,7 @@ func TestIssueService_GetEditMeta_Success(t *testing.T) { }`) }) - editMeta, _, err := testClient.Issue.GetEditMeta(&Issue{Key: "PROJ-9001"}) + editMeta, _, err := testClient.Issue.GetEditMeta(context.Background(), &Issue{Key: "PROJ-9001"}) if err != nil { t.Errorf("Expected nil error but got %s", err) } @@ -446,7 +447,7 @@ func TestIssueService_GetEditMeta_Success(t *testing.T) { } func TestIssueService_GetEditMeta_Fail(t *testing.T) { - _, _, err := testClient.Issue.GetEditMeta(&Issue{Key: "PROJ-9001"}) + _, _, err := testClient.Issue.GetEditMeta(context.Background(), &Issue{Key: "PROJ-9001"}) if err == nil { t.Error("Expected to receive an error, received nil instead") } @@ -797,7 +798,7 @@ func TestMetaIssueType_GetCreateMetaWithOptions(t *testing.T) { }`) }) - issue, _, err := testClient.Issue.GetCreateMetaWithOptions(&GetQueryOptions{Expand: "projects.issuetypes.fields"}) + issue, _, err := testClient.Issue.GetCreateMetaWithOptions(context.Background(), &GetQueryOptions{Expand: "projects.issuetypes.fields"}) if err != nil { t.Errorf("Expected nil error but got %s", err) } diff --git a/onpremise/metaissue.go b/onpremise/metaissue.go index dd35a7f..48d65d6 100644 --- a/onpremise/metaissue.go +++ b/onpremise/metaissue.go @@ -48,18 +48,13 @@ type MetaIssueType struct { Fields tcontainer.MarshalMap `json:"fields,omitempty"` } -// GetCreateMetaWithContext makes the api call to get the meta information required to create a ticket -func (s *IssueService) GetCreateMetaWithContext(ctx context.Context, projectkeys string) (*CreateMetaInfo, *Response, error) { - return s.GetCreateMetaWithOptionsWithContext(ctx, &GetQueryOptions{ProjectKeys: projectkeys, Expand: "projects.issuetypes.fields"}) +// GetCreateMeta makes the api call to get the meta information required to create a ticket +func (s *IssueService) GetCreateMeta(ctx context.Context, projectkeys string) (*CreateMetaInfo, *Response, error) { + return s.GetCreateMetaWithOptions(ctx, &GetQueryOptions{ProjectKeys: projectkeys, Expand: "projects.issuetypes.fields"}) } -// GetCreateMeta wraps GetCreateMetaWithContext using the background context. -func (s *IssueService) GetCreateMeta(projectkeys string) (*CreateMetaInfo, *Response, error) { - return s.GetCreateMetaWithContext(context.Background(), projectkeys) -} - -// GetCreateMetaWithOptionsWithContext makes the api call to get the meta information without requiring to have a projectKey -func (s *IssueService) GetCreateMetaWithOptionsWithContext(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { +// GetCreateMetaWithOptions makes the api call to get the meta information without requiring to have a projectKey +func (s *IssueService) GetCreateMetaWithOptions(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { apiEndpoint := "rest/api/2/issue/createmeta" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) @@ -84,13 +79,8 @@ func (s *IssueService) GetCreateMetaWithOptionsWithContext(ctx context.Context, return meta, resp, nil } -// GetCreateMetaWithOptions wraps GetCreateMetaWithOptionsWithContext using the background context. -func (s *IssueService) GetCreateMetaWithOptions(options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { - return s.GetCreateMetaWithOptionsWithContext(context.Background(), options) -} - -// GetEditMetaWithContext makes the api call to get the edit meta information for an issue -func (s *IssueService) GetEditMetaWithContext(ctx context.Context, issue *Issue) (*EditMetaInfo, *Response, error) { +// GetEditMeta makes the api call to get the edit meta information for an issue +func (s *IssueService) GetEditMeta(ctx context.Context, issue *Issue) (*EditMetaInfo, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/issue/%s/editmeta", issue.Key) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) @@ -108,11 +98,6 @@ func (s *IssueService) GetEditMetaWithContext(ctx context.Context, issue *Issue) return meta, resp, nil } -// GetEditMeta wraps GetEditMetaWithContext using the background context. -func (s *IssueService) GetEditMeta(issue *Issue) (*EditMetaInfo, *Response, error) { - return s.GetEditMetaWithContext(context.Background(), issue) -} - // GetProjectWithName returns a project with "name" from the meta information received. If not found, this returns nil. // The comparison of the name is case insensitive. func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject { diff --git a/onpremise/metaissue_test.go b/onpremise/metaissue_test.go index 6c6e5fb..605cd52 100644 --- a/onpremise/metaissue_test.go +++ b/onpremise/metaissue_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "net/url" @@ -348,7 +349,7 @@ func TestIssueService_GetCreateMeta_Success(t *testing.T) { }`) }) - issue, _, err := testClient.Issue.GetCreateMeta("SPN") + issue, _, err := testClient.Issue.GetCreateMeta(context.Background(), "SPN") if err != nil { t.Errorf("Expected nil error but got %s", err) } @@ -420,7 +421,7 @@ func TestIssueService_GetEditMeta_Success(t *testing.T) { }`) }) - editMeta, _, err := testClient.Issue.GetEditMeta(&Issue{Key: "PROJ-9001"}) + editMeta, _, err := testClient.Issue.GetEditMeta(context.Background(), &Issue{Key: "PROJ-9001"}) if err != nil { t.Errorf("Expected nil error but got %s", err) } @@ -446,7 +447,7 @@ func TestIssueService_GetEditMeta_Success(t *testing.T) { } func TestIssueService_GetEditMeta_Fail(t *testing.T) { - _, _, err := testClient.Issue.GetEditMeta(&Issue{Key: "PROJ-9001"}) + _, _, err := testClient.Issue.GetEditMeta(context.Background(), &Issue{Key: "PROJ-9001"}) if err == nil { t.Error("Expected to receive an error, received nil instead") } @@ -797,7 +798,7 @@ func TestMetaIssueType_GetCreateMetaWithOptions(t *testing.T) { }`) }) - issue, _, err := testClient.Issue.GetCreateMetaWithOptions(&GetQueryOptions{Expand: "projects.issuetypes.fields"}) + issue, _, err := testClient.Issue.GetCreateMetaWithOptions(context.Background(), &GetQueryOptions{Expand: "projects.issuetypes.fields"}) if err != nil { t.Errorf("Expected nil error but got %s", err) } From d7786f06f91038ad46593fe85474ffb6c2f57f9b Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 20:24:45 +0200 Subject: [PATCH 029/189] PermissionScheme Service: Remove "WithContext" API methods --- cloud/permissionscheme.go | 18 ++++-------------- cloud/permissionscheme_test.go | 9 +++++---- onpremise/permissionscheme.go | 18 ++++-------------- onpremise/permissionscheme_test.go | 9 +++++---- 4 files changed, 18 insertions(+), 36 deletions(-) diff --git a/cloud/permissionscheme.go b/cloud/permissionscheme.go index 3f8edcc..e3afa1d 100644 --- a/cloud/permissionscheme.go +++ b/cloud/permissionscheme.go @@ -27,10 +27,10 @@ type Holder struct { Expand string `json:"expand" structs:"expand"` } -// GetListWithContext returns a list of all permission schemes +// GetList returns a list of all permission schemes // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-get -func (s *PermissionSchemeService) GetListWithContext(ctx context.Context) (*PermissionSchemes, *Response, error) { +func (s *PermissionSchemeService) GetList(ctx context.Context) (*PermissionSchemes, *Response, error) { apiEndpoint := "/rest/api/3/permissionscheme" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -47,15 +47,10 @@ func (s *PermissionSchemeService) GetListWithContext(ctx context.Context) (*Perm return pss, resp, nil } -// GetList wraps GetListWithContext using the background context. -func (s *PermissionSchemeService) GetList() (*PermissionSchemes, *Response, error) { - return s.GetListWithContext(context.Background()) -} - -// GetWithContext returns a full representation of the permission scheme for the schemeID +// Get returns a full representation of the permission scheme for the schemeID // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-schemeId-get -func (s *PermissionSchemeService) GetWithContext(ctx context.Context, schemeID int) (*PermissionScheme, *Response, error) { +func (s *PermissionSchemeService) Get(ctx context.Context, schemeID int) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/3/permissionscheme/%d", schemeID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -74,8 +69,3 @@ func (s *PermissionSchemeService) GetWithContext(ctx context.Context, schemeID i return ps, resp, nil } - -// Get wraps GetWithContext using the background context. -func (s *PermissionSchemeService) Get(schemeID int) (*PermissionScheme, *Response, error) { - return s.GetWithContext(context.Background(), schemeID) -} diff --git a/cloud/permissionscheme_test.go b/cloud/permissionscheme_test.go index 8cddb72..299c57a 100644 --- a/cloud/permissionscheme_test.go +++ b/cloud/permissionscheme_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestPermissionSchemeService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - permissionScheme, _, err := testClient.PermissionScheme.GetList() + permissionScheme, _, err := testClient.PermissionScheme.GetList(context.Background()) if err != nil { t.Errorf("Error given: %v", err) } @@ -50,7 +51,7 @@ func TestPermissionSchemeService_GetList_NoList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - permissionScheme, _, err := testClient.PermissionScheme.GetList() + permissionScheme, _, err := testClient.PermissionScheme.GetList(context.Background()) if permissionScheme != nil { t.Errorf("Expected permissionScheme list has %d entries but should be nil", len(permissionScheme.PermissionSchemes)) } @@ -73,7 +74,7 @@ func TestPermissionSchemeService_Get(t *testing.T) { fmt.Fprint(writer, string(raw)) }) - permissionScheme, _, err := testClient.PermissionScheme.Get(10100) + permissionScheme, _, err := testClient.PermissionScheme.Get(context.Background(), 10100) if permissionScheme == nil { t.Errorf("Expected permissionscheme, got nil") } @@ -96,7 +97,7 @@ func TestPermissionSchemeService_Get_NoScheme(t *testing.T) { fmt.Fprint(writer, string(raw)) }) - permissionScheme, _, err := testClient.PermissionScheme.Get(99999) + permissionScheme, _, err := testClient.PermissionScheme.Get(context.Background(), 99999) if permissionScheme != nil { t.Errorf("Expected nil, got permissionschme %v", permissionScheme) } diff --git a/onpremise/permissionscheme.go b/onpremise/permissionscheme.go index 3257eed..8f7d337 100644 --- a/onpremise/permissionscheme.go +++ b/onpremise/permissionscheme.go @@ -27,10 +27,10 @@ type Holder struct { Expand string `json:"expand" structs:"expand"` } -// GetListWithContext returns a list of all permission schemes +// GetList returns a list of all permission schemes // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-get -func (s *PermissionSchemeService) GetListWithContext(ctx context.Context) (*PermissionSchemes, *Response, error) { +func (s *PermissionSchemeService) GetList(ctx context.Context) (*PermissionSchemes, *Response, error) { apiEndpoint := "/rest/api/3/permissionscheme" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -47,15 +47,10 @@ func (s *PermissionSchemeService) GetListWithContext(ctx context.Context) (*Perm return pss, resp, nil } -// GetList wraps GetListWithContext using the background context. -func (s *PermissionSchemeService) GetList() (*PermissionSchemes, *Response, error) { - return s.GetListWithContext(context.Background()) -} - -// GetWithContext returns a full representation of the permission scheme for the schemeID +// Get returns a full representation of the permission scheme for the schemeID // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-schemeId-get -func (s *PermissionSchemeService) GetWithContext(ctx context.Context, schemeID int) (*PermissionScheme, *Response, error) { +func (s *PermissionSchemeService) Get(ctx context.Context, schemeID int) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/3/permissionscheme/%d", schemeID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -74,8 +69,3 @@ func (s *PermissionSchemeService) GetWithContext(ctx context.Context, schemeID i return ps, resp, nil } - -// Get wraps GetWithContext using the background context. -func (s *PermissionSchemeService) Get(schemeID int) (*PermissionScheme, *Response, error) { - return s.GetWithContext(context.Background(), schemeID) -} diff --git a/onpremise/permissionscheme_test.go b/onpremise/permissionscheme_test.go index 206efdf..8cd8c3e 100644 --- a/onpremise/permissionscheme_test.go +++ b/onpremise/permissionscheme_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestPermissionSchemeService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - permissionScheme, _, err := testClient.PermissionScheme.GetList() + permissionScheme, _, err := testClient.PermissionScheme.GetList(context.Background()) if err != nil { t.Errorf("Error given: %v", err) } @@ -50,7 +51,7 @@ func TestPermissionSchemeService_GetList_NoList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - permissionScheme, _, err := testClient.PermissionScheme.GetList() + permissionScheme, _, err := testClient.PermissionScheme.GetList(context.Background()) if permissionScheme != nil { t.Errorf("Expected permissionScheme list has %d entries but should be nil", len(permissionScheme.PermissionSchemes)) } @@ -73,7 +74,7 @@ func TestPermissionSchemeService_Get(t *testing.T) { fmt.Fprint(writer, string(raw)) }) - permissionScheme, _, err := testClient.PermissionScheme.Get(10100) + permissionScheme, _, err := testClient.PermissionScheme.Get(context.Background(), 10100) if permissionScheme == nil { t.Errorf("Expected permissionscheme, got nil") } @@ -96,7 +97,7 @@ func TestPermissionSchemeService_Get_NoScheme(t *testing.T) { fmt.Fprint(writer, string(raw)) }) - permissionScheme, _, err := testClient.PermissionScheme.Get(99999) + permissionScheme, _, err := testClient.PermissionScheme.Get(context.Background(), 99999) if permissionScheme != nil { t.Errorf("Expected nil, got permissionschme %v", permissionScheme) } From 4eca2c22eb6353351ba4884c17199fbfa9120d5c Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 20:24:57 +0200 Subject: [PATCH 030/189] Priority Service: Remove "WithContext" API methods --- cloud/priority.go | 9 ++------- cloud/priority_test.go | 3 ++- onpremise/priority.go | 9 ++------- onpremise/priority_test.go | 3 ++- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/cloud/priority.go b/cloud/priority.go index 7f62cd4..a431d74 100644 --- a/cloud/priority.go +++ b/cloud/priority.go @@ -18,10 +18,10 @@ type Priority struct { Description string `json:"description,omitempty" structs:"description,omitempty"` } -// GetListWithContext gets all priorities from Jira +// GetList gets all priorities from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-priority-get -func (s *PriorityService) GetListWithContext(ctx context.Context) ([]Priority, *Response, error) { +func (s *PriorityService) GetList(ctx context.Context) ([]Priority, *Response, error) { apiEndpoint := "rest/api/2/priority" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -35,8 +35,3 @@ func (s *PriorityService) GetListWithContext(ctx context.Context) ([]Priority, * } return priorityList, resp, nil } - -// GetList wraps GetListWithContext using the background context. -func (s *PriorityService) GetList() ([]Priority, *Response, error) { - return s.GetListWithContext(context.Background()) -} diff --git a/cloud/priority_test.go b/cloud/priority_test.go index af6305c..5994888 100644 --- a/cloud/priority_test.go +++ b/cloud/priority_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestPriorityService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - priorities, _, err := testClient.Priority.GetList() + priorities, _, err := testClient.Priority.GetList(context.Background()) if priorities == nil { t.Error("Expected priority list. Priority list is nil") } diff --git a/onpremise/priority.go b/onpremise/priority.go index 9ec0dbe..8e928ae 100644 --- a/onpremise/priority.go +++ b/onpremise/priority.go @@ -18,10 +18,10 @@ type Priority struct { Description string `json:"description,omitempty" structs:"description,omitempty"` } -// GetListWithContext gets all priorities from Jira +// GetList gets all priorities from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-priority-get -func (s *PriorityService) GetListWithContext(ctx context.Context) ([]Priority, *Response, error) { +func (s *PriorityService) GetList(ctx context.Context) ([]Priority, *Response, error) { apiEndpoint := "rest/api/2/priority" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -35,8 +35,3 @@ func (s *PriorityService) GetListWithContext(ctx context.Context) ([]Priority, * } return priorityList, resp, nil } - -// GetList wraps GetListWithContext using the background context. -func (s *PriorityService) GetList() ([]Priority, *Response, error) { - return s.GetListWithContext(context.Background()) -} diff --git a/onpremise/priority_test.go b/onpremise/priority_test.go index e2ca273..5c15bc4 100644 --- a/onpremise/priority_test.go +++ b/onpremise/priority_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestPriorityService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - priorities, _, err := testClient.Priority.GetList() + priorities, _, err := testClient.Priority.GetList(context.Background()) if priorities == nil { t.Error("Expected priority list. Priority list is nil") } From 85afd339d582f6d121b5a19915dee6c8ef513f6d Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 20:34:23 +0200 Subject: [PATCH 031/189] Component Service: Remove "WithContext" API methods --- cloud/component.go | 9 ++------- cloud/component_test.go | 3 ++- onpremise/component.go | 9 ++------- onpremise/component_test.go | 3 ++- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/cloud/component.go b/cloud/component.go index ad18e43..04fc131 100644 --- a/cloud/component.go +++ b/cloud/component.go @@ -18,8 +18,8 @@ type CreateComponentOptions struct { ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"` } -// CreateWithContext creates a new Jira component based on the given options. -func (s *ComponentService) CreateWithContext(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) { +// Create creates a new Jira component based on the given options. +func (s *ComponentService) Create(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) { apiEndpoint := "rest/api/2/component" req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, options) if err != nil { @@ -35,8 +35,3 @@ func (s *ComponentService) CreateWithContext(ctx context.Context, options *Creat return component, resp, nil } - -// Create wraps CreateWithContext using the background context. -func (s *ComponentService) Create(options *CreateComponentOptions) (*ProjectComponent, *Response, error) { - return s.CreateWithContext(context.Background(), options) -} diff --git a/cloud/component_test.go b/cloud/component_test.go index e071945..15bcbbc 100644 --- a/cloud/component_test.go +++ b/cloud/component_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "testing" @@ -17,7 +18,7 @@ func TestComponentService_Create_Success(t *testing.T) { fmt.Fprint(w, `{ "self": "http://www.example.com/jira/rest/api/2/component/10000", "id": "10000", "name": "Component 1", "description": "This is a Jira component", "lead": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "avatarUrls": { "48x48": "http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", "24x24": "http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred", "16x16": "http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", "32x32": "http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred" }, "displayName": "Fred F. User", "active": false }, "assigneeType": "PROJECT_LEAD", "assignee": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "avatarUrls": { "48x48": "http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", "24x24": "http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred", "16x16": "http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", "32x32": "http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred" }, "displayName": "Fred F. User", "active": false }, "realAssigneeType": "PROJECT_LEAD", "realAssignee": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "avatarUrls": { "48x48": "http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", "24x24": "http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred", "16x16": "http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", "32x32": "http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred" }, "displayName": "Fred F. User", "active": false }, "isAssigneeTypeValid": false, "project": "HSP", "projectId": 10000 }`) }) - component, _, err := testClient.Component.Create(&CreateComponentOptions{ + component, _, err := testClient.Component.Create(context.Background(), &CreateComponentOptions{ Name: "foo-bar", }) if component == nil { diff --git a/onpremise/component.go b/onpremise/component.go index c1ac05d..3d10d6d 100644 --- a/onpremise/component.go +++ b/onpremise/component.go @@ -18,8 +18,8 @@ type CreateComponentOptions struct { ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"` } -// CreateWithContext creates a new Jira component based on the given options. -func (s *ComponentService) CreateWithContext(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) { +// Create creates a new Jira component based on the given options. +func (s *ComponentService) Create(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) { apiEndpoint := "rest/api/2/component" req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, options) if err != nil { @@ -35,8 +35,3 @@ func (s *ComponentService) CreateWithContext(ctx context.Context, options *Creat return component, resp, nil } - -// Create wraps CreateWithContext using the background context. -func (s *ComponentService) Create(options *CreateComponentOptions) (*ProjectComponent, *Response, error) { - return s.CreateWithContext(context.Background(), options) -} diff --git a/onpremise/component_test.go b/onpremise/component_test.go index bc60e09..f7f4f66 100644 --- a/onpremise/component_test.go +++ b/onpremise/component_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "testing" @@ -17,7 +18,7 @@ func TestComponentService_Create_Success(t *testing.T) { fmt.Fprint(w, `{ "self": "http://www.example.com/jira/rest/api/2/component/10000", "id": "10000", "name": "Component 1", "description": "This is a Jira component", "lead": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "avatarUrls": { "48x48": "http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", "24x24": "http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred", "16x16": "http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", "32x32": "http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred" }, "displayName": "Fred F. User", "active": false }, "assigneeType": "PROJECT_LEAD", "assignee": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "avatarUrls": { "48x48": "http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", "24x24": "http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred", "16x16": "http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", "32x32": "http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred" }, "displayName": "Fred F. User", "active": false }, "realAssigneeType": "PROJECT_LEAD", "realAssignee": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "avatarUrls": { "48x48": "http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", "24x24": "http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred", "16x16": "http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", "32x32": "http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred" }, "displayName": "Fred F. User", "active": false }, "isAssigneeTypeValid": false, "project": "HSP", "projectId": 10000 }`) }) - component, _, err := testClient.Component.Create(&CreateComponentOptions{ + component, _, err := testClient.Component.Create(context.Background(), &CreateComponentOptions{ Name: "foo-bar", }) if component == nil { From d588147718e1753de331f8ea77948cbb51d5046d Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 20:34:48 +0200 Subject: [PATCH 032/189] Customer Service: Remove "WithContext" API methods --- cloud/customer.go | 9 ++------- cloud/customer_test.go | 3 ++- onpremise/customer.go | 9 ++------- onpremise/customer_test.go | 3 ++- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/cloud/customer.go b/cloud/customer.go index 095c81e..d1adc3e 100644 --- a/cloud/customer.go +++ b/cloud/customer.go @@ -36,10 +36,10 @@ type CustomerList struct { Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"` } -// CreateWithContext creates a ServiceDesk customer. +// Create creates a ServiceDesk customer. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-customer/#api-rest-servicedeskapi-customer-post -func (c *CustomerService) CreateWithContext(ctx context.Context, email, displayName string) (*Customer, *Response, error) { +func (c *CustomerService) Create(ctx context.Context, email, displayName string) (*Customer, *Response, error) { const apiEndpoint = "rest/servicedeskapi/customer" payload := struct { @@ -63,8 +63,3 @@ func (c *CustomerService) CreateWithContext(ctx context.Context, email, displayN return responseCustomer, resp, nil } - -// Create wraps CreateWithContext using the background context. -func (c *CustomerService) Create(email, displayName string) (*Customer, *Response, error) { - return c.CreateWithContext(context.Background(), email, displayName) -} diff --git a/cloud/customer_test.go b/cloud/customer_test.go index d12db6b..bce6a12 100644 --- a/cloud/customer_test.go +++ b/cloud/customer_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "testing" @@ -41,7 +42,7 @@ func TestCustomerService_Create(t *testing.T) { }`, wantEmailAddress, wantDisplayName) }) - gotCustomer, _, err := testClient.Customer.Create(wantEmailAddress, wantDisplayName) + gotCustomer, _, err := testClient.Customer.Create(context.Background(), wantEmailAddress, wantDisplayName) if err != nil { t.Fatal(err) } diff --git a/onpremise/customer.go b/onpremise/customer.go index 21e9013..b7c11c9 100644 --- a/onpremise/customer.go +++ b/onpremise/customer.go @@ -36,10 +36,10 @@ type CustomerList struct { Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"` } -// CreateWithContext creates a ServiceDesk customer. +// Create creates a ServiceDesk customer. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-customer/#api-rest-servicedeskapi-customer-post -func (c *CustomerService) CreateWithContext(ctx context.Context, email, displayName string) (*Customer, *Response, error) { +func (c *CustomerService) Create(ctx context.Context, email, displayName string) (*Customer, *Response, error) { const apiEndpoint = "rest/servicedeskapi/customer" payload := struct { @@ -63,8 +63,3 @@ func (c *CustomerService) CreateWithContext(ctx context.Context, email, displayN return responseCustomer, resp, nil } - -// Create wraps CreateWithContext using the background context. -func (c *CustomerService) Create(email, displayName string) (*Customer, *Response, error) { - return c.CreateWithContext(context.Background(), email, displayName) -} diff --git a/onpremise/customer_test.go b/onpremise/customer_test.go index db88657..835381c 100644 --- a/onpremise/customer_test.go +++ b/onpremise/customer_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "testing" @@ -41,7 +42,7 @@ func TestCustomerService_Create(t *testing.T) { }`, wantEmailAddress, wantDisplayName) }) - gotCustomer, _, err := testClient.Customer.Create(wantEmailAddress, wantDisplayName) + gotCustomer, _, err := testClient.Customer.Create(context.Background(), wantEmailAddress, wantDisplayName) if err != nil { t.Fatal(err) } From 3a203a54534866551eeeaddfea7dc0f6d5aaca1b Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 20:35:06 +0200 Subject: [PATCH 033/189] Field Service: Remove "WithContext" API methods --- cloud/field.go | 9 ++------- cloud/field_test.go | 3 ++- onpremise/field.go | 9 ++------- onpremise/field_test.go | 3 ++- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/cloud/field.go b/cloud/field.go index c53a14f..5fbbc0f 100644 --- a/cloud/field.go +++ b/cloud/field.go @@ -29,10 +29,10 @@ type FieldSchema struct { CustomID int64 `json:"customId,omitempty" structs:"customId,omitempty"` } -// GetListWithContext gets all fields from Jira +// GetList gets all fields from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get -func (s *FieldService) GetListWithContext(ctx context.Context) ([]Field, *Response, error) { +func (s *FieldService) GetList(ctx context.Context) ([]Field, *Response, error) { apiEndpoint := "rest/api/2/field" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -46,8 +46,3 @@ func (s *FieldService) GetListWithContext(ctx context.Context) ([]Field, *Respon } return fieldList, resp, nil } - -// GetList wraps GetListWithContext using the background context. -func (s *FieldService) GetList() ([]Field, *Response, error) { - return s.GetListWithContext(context.Background()) -} diff --git a/cloud/field_test.go b/cloud/field_test.go index 55ef9f5..93aade9 100644 --- a/cloud/field_test.go +++ b/cloud/field_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestFieldService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - fields, _, err := testClient.Field.GetList() + fields, _, err := testClient.Field.GetList(context.Background()) if fields == nil { t.Error("Expected field list. Field list is nil") } diff --git a/onpremise/field.go b/onpremise/field.go index 7747721..054d853 100644 --- a/onpremise/field.go +++ b/onpremise/field.go @@ -29,10 +29,10 @@ type FieldSchema struct { CustomID int64 `json:"customId,omitempty" structs:"customId,omitempty"` } -// GetListWithContext gets all fields from Jira +// GetList gets all fields from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get -func (s *FieldService) GetListWithContext(ctx context.Context) ([]Field, *Response, error) { +func (s *FieldService) GetList(ctx context.Context) ([]Field, *Response, error) { apiEndpoint := "rest/api/2/field" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -46,8 +46,3 @@ func (s *FieldService) GetListWithContext(ctx context.Context) ([]Field, *Respon } return fieldList, resp, nil } - -// GetList wraps GetListWithContext using the background context. -func (s *FieldService) GetList() ([]Field, *Response, error) { - return s.GetListWithContext(context.Background()) -} diff --git a/onpremise/field_test.go b/onpremise/field_test.go index 763d5b6..59cf831 100644 --- a/onpremise/field_test.go +++ b/onpremise/field_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestFieldService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - fields, _, err := testClient.Field.GetList() + fields, _, err := testClient.Field.GetList(context.Background()) if fields == nil { t.Error("Expected field list. Field list is nil") } From a118968384abf411b0b3161f40abbbeec82a9593 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 20:35:24 +0200 Subject: [PATCH 034/189] Filter Service: Remove "WithContext" API methods --- cloud/filter.go | 45 +++++++++------------------------------- cloud/filter_test.go | 11 +++++----- onpremise/filter.go | 45 +++++++++------------------------------- onpremise/filter_test.go | 11 +++++----- 4 files changed, 32 insertions(+), 80 deletions(-) diff --git a/cloud/filter.go b/cloud/filter.go index a92e0e5..52f942b 100644 --- a/cloud/filter.go +++ b/cloud/filter.go @@ -118,8 +118,8 @@ type FilterSearchOptions struct { Expand string `url:"expand,omitempty"` } -// GetListWithContext retrieves all filters from Jira -func (fs *FilterService) GetListWithContext(ctx context.Context) ([]*Filter, *Response, error) { +// GetList retrieves all filters from Jira +func (fs *FilterService) GetList(ctx context.Context) ([]*Filter, *Response, error) { options := &GetQueryOptions{} apiEndpoint := "rest/api/2/filter" @@ -143,13 +143,8 @@ func (fs *FilterService) GetListWithContext(ctx context.Context) ([]*Filter, *Re return filters, resp, err } -// GetList wraps GetListWithContext using the background context. -func (fs *FilterService) GetList() ([]*Filter, *Response, error) { - return fs.GetListWithContext(context.Background()) -} - -// GetFavouriteListWithContext retrieves the user's favourited filters from Jira -func (fs *FilterService) GetFavouriteListWithContext(ctx context.Context) ([]*Filter, *Response, error) { +// GetFavouriteList retrieves the user's favourited filters from Jira +func (fs *FilterService) GetFavouriteList(ctx context.Context) ([]*Filter, *Response, error) { apiEndpoint := "rest/api/2/filter/favourite" req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -164,13 +159,8 @@ func (fs *FilterService) GetFavouriteListWithContext(ctx context.Context) ([]*Fi return filters, resp, err } -// GetFavouriteList wraps GetFavouriteListWithContext using the background context. -func (fs *FilterService) GetFavouriteList() ([]*Filter, *Response, error) { - return fs.GetFavouriteListWithContext(context.Background()) -} - -// GetWithContext retrieves a single Filter from Jira -func (fs *FilterService) GetWithContext(ctx context.Context, filterID int) (*Filter, *Response, error) { +// Get retrieves a single Filter from Jira +func (fs *FilterService) Get(ctx context.Context, filterID int) (*Filter, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/filter/%d", filterID) req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -186,15 +176,10 @@ func (fs *FilterService) GetWithContext(ctx context.Context, filterID int) (*Fil return filter, resp, err } -// Get wraps GetWithContext using the background context. -func (fs *FilterService) Get(filterID int) (*Filter, *Response, error) { - return fs.GetWithContext(context.Background(), filterID) -} - -// GetMyFiltersWithContext retrieves the my Filters. +// GetMyFilters retrieves the my Filters. // // https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-my-get -func (fs *FilterService) GetMyFiltersWithContext(ctx context.Context, opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) { +func (fs *FilterService) GetMyFilters(ctx context.Context, opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) { apiEndpoint := "rest/api/3/filter/my" url, err := addOptions(apiEndpoint, opts) if err != nil { @@ -214,15 +199,10 @@ func (fs *FilterService) GetMyFiltersWithContext(ctx context.Context, opts *GetM return filters, resp, nil } -// GetMyFilters wraps GetMyFiltersWithContext using the background context. -func (fs *FilterService) GetMyFilters(opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) { - return fs.GetMyFiltersWithContext(context.Background(), opts) -} - -// SearchWithContext will search for filter according to the search options +// Search will search for filter according to the search options // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-search-get -func (fs *FilterService) SearchWithContext(ctx context.Context, opt *FilterSearchOptions) (*FiltersList, *Response, error) { +func (fs *FilterService) Search(ctx context.Context, opt *FilterSearchOptions) (*FiltersList, *Response, error) { apiEndpoint := "rest/api/3/filter/search" url, err := addOptions(apiEndpoint, opt) if err != nil { @@ -242,8 +222,3 @@ func (fs *FilterService) SearchWithContext(ctx context.Context, opt *FilterSearc return filters, resp, err } - -// Search wraps SearchWithContext using the background context. -func (fs *FilterService) Search(opt *FilterSearchOptions) (*FiltersList, *Response, error) { - return fs.SearchWithContext(context.Background(), opt) -} diff --git a/cloud/filter_test.go b/cloud/filter_test.go index add1acc..e84fda0 100644 --- a/cloud/filter_test.go +++ b/cloud/filter_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "os" @@ -21,7 +22,7 @@ func TestFilterService_GetList(t *testing.T) { fmt.Fprint(writer, string(raw)) }) - filters, _, err := testClient.Filter.GetList() + filters, _, err := testClient.Filter.GetList(context.Background()) if filters == nil { t.Error("Expected Filters list. Filters list is nil") } @@ -44,7 +45,7 @@ func TestFilterService_Get(t *testing.T) { fmt.Fprint(writer, string(raw)) }) - filter, _, err := testClient.Filter.Get(10000) + filter, _, err := testClient.Filter.Get(context.Background(), 10000) if filter == nil { t.Errorf("Expected Filter, got nil") } @@ -68,7 +69,7 @@ func TestFilterService_GetFavouriteList(t *testing.T) { fmt.Fprint(writer, string(raw)) }) - filters, _, err := testClient.Filter.GetFavouriteList() + filters, _, err := testClient.Filter.GetFavouriteList(context.Background()) if filters == nil { t.Error("Expected Filters list. Filters list is nil") } @@ -92,7 +93,7 @@ func TestFilterService_GetMyFilters(t *testing.T) { }) opts := GetMyFiltersQueryOptions{} - filters, _, err := testClient.Filter.GetMyFilters(&opts) + filters, _, err := testClient.Filter.GetMyFilters(context.Background(), &opts) if err != nil { t.Errorf("Error given: %s", err) } @@ -116,7 +117,7 @@ func TestFilterService_Search(t *testing.T) { }) opt := FilterSearchOptions{} - filters, _, err := testClient.Filter.Search(&opt) + filters, _, err := testClient.Filter.Search(context.Background(), &opt) if err != nil { t.Errorf("Error given: %s", err) } diff --git a/onpremise/filter.go b/onpremise/filter.go index 4652429..6bf3074 100644 --- a/onpremise/filter.go +++ b/onpremise/filter.go @@ -118,8 +118,8 @@ type FilterSearchOptions struct { Expand string `url:"expand,omitempty"` } -// GetListWithContext retrieves all filters from Jira -func (fs *FilterService) GetListWithContext(ctx context.Context) ([]*Filter, *Response, error) { +// GetList retrieves all filters from Jira +func (fs *FilterService) GetList(ctx context.Context) ([]*Filter, *Response, error) { options := &GetQueryOptions{} apiEndpoint := "rest/api/2/filter" @@ -143,13 +143,8 @@ func (fs *FilterService) GetListWithContext(ctx context.Context) ([]*Filter, *Re return filters, resp, err } -// GetList wraps GetListWithContext using the background context. -func (fs *FilterService) GetList() ([]*Filter, *Response, error) { - return fs.GetListWithContext(context.Background()) -} - -// GetFavouriteListWithContext retrieves the user's favourited filters from Jira -func (fs *FilterService) GetFavouriteListWithContext(ctx context.Context) ([]*Filter, *Response, error) { +// GetFavouriteList retrieves the user's favourited filters from Jira +func (fs *FilterService) GetFavouriteList(ctx context.Context) ([]*Filter, *Response, error) { apiEndpoint := "rest/api/2/filter/favourite" req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -164,13 +159,8 @@ func (fs *FilterService) GetFavouriteListWithContext(ctx context.Context) ([]*Fi return filters, resp, err } -// GetFavouriteList wraps GetFavouriteListWithContext using the background context. -func (fs *FilterService) GetFavouriteList() ([]*Filter, *Response, error) { - return fs.GetFavouriteListWithContext(context.Background()) -} - -// GetWithContext retrieves a single Filter from Jira -func (fs *FilterService) GetWithContext(ctx context.Context, filterID int) (*Filter, *Response, error) { +// Get retrieves a single Filter from Jira +func (fs *FilterService) Get(ctx context.Context, filterID int) (*Filter, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/filter/%d", filterID) req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -186,15 +176,10 @@ func (fs *FilterService) GetWithContext(ctx context.Context, filterID int) (*Fil return filter, resp, err } -// Get wraps GetWithContext using the background context. -func (fs *FilterService) Get(filterID int) (*Filter, *Response, error) { - return fs.GetWithContext(context.Background(), filterID) -} - -// GetMyFiltersWithContext retrieves the my Filters. +// GetMyFilters retrieves the my Filters. // // https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-my-get -func (fs *FilterService) GetMyFiltersWithContext(ctx context.Context, opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) { +func (fs *FilterService) GetMyFilters(ctx context.Context, opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) { apiEndpoint := "rest/api/3/filter/my" url, err := addOptions(apiEndpoint, opts) if err != nil { @@ -214,15 +199,10 @@ func (fs *FilterService) GetMyFiltersWithContext(ctx context.Context, opts *GetM return filters, resp, nil } -// GetMyFilters wraps GetMyFiltersWithContext using the background context. -func (fs *FilterService) GetMyFilters(opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) { - return fs.GetMyFiltersWithContext(context.Background(), opts) -} - -// SearchWithContext will search for filter according to the search options +// Search will search for filter according to the search options // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-search-get -func (fs *FilterService) SearchWithContext(ctx context.Context, opt *FilterSearchOptions) (*FiltersList, *Response, error) { +func (fs *FilterService) Search(ctx context.Context, opt *FilterSearchOptions) (*FiltersList, *Response, error) { apiEndpoint := "rest/api/3/filter/search" url, err := addOptions(apiEndpoint, opt) if err != nil { @@ -242,8 +222,3 @@ func (fs *FilterService) SearchWithContext(ctx context.Context, opt *FilterSearc return filters, resp, err } - -// Search wraps SearchWithContext using the background context. -func (fs *FilterService) Search(opt *FilterSearchOptions) (*FiltersList, *Response, error) { - return fs.SearchWithContext(context.Background(), opt) -} diff --git a/onpremise/filter_test.go b/onpremise/filter_test.go index 507753e..d3d9c91 100644 --- a/onpremise/filter_test.go +++ b/onpremise/filter_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "os" @@ -21,7 +22,7 @@ func TestFilterService_GetList(t *testing.T) { fmt.Fprint(writer, string(raw)) }) - filters, _, err := testClient.Filter.GetList() + filters, _, err := testClient.Filter.GetList(context.Background()) if filters == nil { t.Error("Expected Filters list. Filters list is nil") } @@ -44,7 +45,7 @@ func TestFilterService_Get(t *testing.T) { fmt.Fprint(writer, string(raw)) }) - filter, _, err := testClient.Filter.Get(10000) + filter, _, err := testClient.Filter.Get(context.Background(), 10000) if filter == nil { t.Errorf("Expected Filter, got nil") } @@ -68,7 +69,7 @@ func TestFilterService_GetFavouriteList(t *testing.T) { fmt.Fprint(writer, string(raw)) }) - filters, _, err := testClient.Filter.GetFavouriteList() + filters, _, err := testClient.Filter.GetFavouriteList(context.Background()) if filters == nil { t.Error("Expected Filters list. Filters list is nil") } @@ -92,7 +93,7 @@ func TestFilterService_GetMyFilters(t *testing.T) { }) opts := GetMyFiltersQueryOptions{} - filters, _, err := testClient.Filter.GetMyFilters(&opts) + filters, _, err := testClient.Filter.GetMyFilters(context.Background(), &opts) if err != nil { t.Errorf("Error given: %s", err) } @@ -116,7 +117,7 @@ func TestFilterService_Search(t *testing.T) { }) opt := FilterSearchOptions{} - filters, _, err := testClient.Filter.Search(&opt) + filters, _, err := testClient.Filter.Search(context.Background(), &opt) if err != nil { t.Errorf("Error given: %s", err) } From a5105d9b6c99f6877609842c2ef627bd7857dcff Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 20:35:45 +0200 Subject: [PATCH 035/189] Group Service: Remove "WithContext" API methods --- cloud/group.go | 37 ++++++++----------------------------- cloud/group_test.go | 11 ++++++----- onpremise/group.go | 37 ++++++++----------------------------- onpremise/group_test.go | 11 ++++++----- 4 files changed, 28 insertions(+), 68 deletions(-) diff --git a/cloud/group.go b/cloud/group.go index 29a70d1..1b30c3a 100644 --- a/cloud/group.go +++ b/cloud/group.go @@ -57,14 +57,14 @@ type GroupSearchOptions struct { IncludeInactiveUsers bool } -// GetWithContext returns a paginated list of users who are members of the specified group and its subgroups. +// Get returns a paginated list of users who are members of the specified group and its subgroups. // Users in the page are ordered by user names. // User of this resource is required to have sysadmin or admin permissions. // // Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup // // WARNING: This API only returns the first page of group members -func (s *GroupService) GetWithContext(ctx context.Context, name string) ([]GroupMember, *Response, error) { +func (s *GroupService) Get(ctx context.Context, name string) ([]GroupMember, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -80,17 +80,12 @@ func (s *GroupService) GetWithContext(ctx context.Context, name string) ([]Group return group.Members, resp, nil } -// Get wraps GetWithContext using the background context. -func (s *GroupService) Get(name string) ([]GroupMember, *Response, error) { - return s.GetWithContext(context.Background(), name) -} - -// GetWithOptionsWithContext returns a paginated list of members of the specified group and its subgroups. +// GetWithOptions returns a paginated list of members of the specified group and its subgroups. // Users in the page are ordered by user names. // User of this resource is required to have sysadmin or admin permissions. // // Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup -func (s *GroupService) GetWithOptionsWithContext(ctx context.Context, name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { +func (s *GroupService) GetWithOptions(ctx context.Context, name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { var apiEndpoint string if options == nil { apiEndpoint = fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) @@ -116,15 +111,10 @@ func (s *GroupService) GetWithOptionsWithContext(ctx context.Context, name strin return group.Members, resp, nil } -// GetWithOptions wraps GetWithOptionsWithContext using the background context. -func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { - return s.GetWithOptionsWithContext(context.Background(), name, options) -} - -// AddWithContext adds user to group +// Add adds user to group // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-addUserToGroup -func (s *GroupService) AddWithContext(ctx context.Context, groupname string, username string) (*Group, *Response, error) { +func (s *GroupService) Add(ctx context.Context, groupname string, username string) (*Group, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s", groupname) var user struct { Name string `json:"name"` @@ -145,16 +135,11 @@ func (s *GroupService) AddWithContext(ctx context.Context, groupname string, use return responseGroup, resp, nil } -// Add wraps AddWithContext using the background context. -func (s *GroupService) Add(groupname string, username string) (*Group, *Response, error) { - return s.AddWithContext(context.Background(), groupname, username) -} - -// RemoveWithContext removes user from group +// Remove removes user from group // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-removeUserFromGroup // Caller must close resp.Body -func (s *GroupService) RemoveWithContext(ctx context.Context, groupname string, username string) (*Response, error) { +func (s *GroupService) Remove(ctx context.Context, groupname string, username string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s&username=%s", groupname, username) req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { @@ -169,9 +154,3 @@ func (s *GroupService) RemoveWithContext(ctx context.Context, groupname string, return resp, nil } - -// Remove wraps RemoveWithContext using the background context. -// Caller must close resp.Body -func (s *GroupService) Remove(groupname string, username string) (*Response, error) { - return s.RemoveWithContext(context.Background(), groupname, username) -} diff --git a/cloud/group_test.go b/cloud/group_test.go index 98b48f2..f95ad44 100644 --- a/cloud/group_test.go +++ b/cloud/group_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "testing" @@ -14,7 +15,7 @@ func TestGroupService_Get(t *testing.T) { testRequestURL(t, r, "/rest/api/2/group/member?groupname=default") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/group/member?includeInactiveUsers=false&maxResults=50&groupname=default&startAt=0","maxResults":50,"startAt":0,"total":2,"isLast":true,"values":[{"self":"http://www.example.com/jira/rest/api/2/user?username=michael","name":"michael","key":"michael","emailAddress":"michael@example.com","displayName":"MichaelScofield","active":true,"timeZone":"Australia/Sydney"},{"self":"http://www.example.com/jira/rest/api/2/user?username=alex","name":"alex","key":"alex","emailAddress":"alex@example.com","displayName":"AlexanderMahone","active":true,"timeZone":"Australia/Sydney"}]}`) }) - if members, _, err := testClient.Group.Get("default"); err != nil { + if members, _, err := testClient.Group.Get(context.Background(), "default"); err != nil { t.Errorf("Error given: %s", err) } else if members == nil { t.Error("Expected members. Group.Members is nil") @@ -36,7 +37,7 @@ func TestGroupService_GetPage(t *testing.T) { t.Errorf("startAt %s", startAt) } }) - if page, resp, err := testClient.Group.GetWithOptions("default", &GroupSearchOptions{ + if page, resp, err := testClient.Group.GetWithOptions(context.Background(), "default", &GroupSearchOptions{ StartAt: 0, MaxResults: 2, IncludeInactiveUsers: false, @@ -54,7 +55,7 @@ func TestGroupService_GetPage(t *testing.T) { if resp.Total != 4 { t.Errorf("Expect Result Total to be 4, but is %d", resp.Total) } - if page, resp, err := testClient.Group.GetWithOptions("default", &GroupSearchOptions{ + if page, resp, err := testClient.Group.GetWithOptions(context.Background(), "default", &GroupSearchOptions{ StartAt: 2, MaxResults: 2, IncludeInactiveUsers: false, @@ -87,7 +88,7 @@ func TestGroupService_Add(t *testing.T) { fmt.Fprint(w, `{"name":"default","self":"http://www.example.com/jira/rest/api/2/group?groupname=default","users":{"size":1,"items":[],"max-results":50,"start-index":0,"end-index":0},"expand":"users"}`) }) - if group, _, err := testClient.Group.Add("default", "theodore"); err != nil { + if group, _, err := testClient.Group.Add(context.Background(), "default", "theodore"); err != nil { t.Errorf("Error given: %s", err) } else if group == nil { t.Error("Expected group. Group is nil") @@ -105,7 +106,7 @@ func TestGroupService_Remove(t *testing.T) { fmt.Fprint(w, `{"name":"default","self":"http://www.example.com/jira/rest/api/2/group?groupname=default","users":{"size":1,"items":[],"max-results":50,"start-index":0,"end-index":0},"expand":"users"}`) }) - if _, err := testClient.Group.Remove("default", "theodore"); err != nil { + if _, err := testClient.Group.Remove(context.Background(), "default", "theodore"); err != nil { t.Errorf("Error given: %s", err) } } diff --git a/onpremise/group.go b/onpremise/group.go index 12a0cf5..98e984c 100644 --- a/onpremise/group.go +++ b/onpremise/group.go @@ -57,14 +57,14 @@ type GroupSearchOptions struct { IncludeInactiveUsers bool } -// GetWithContext returns a paginated list of users who are members of the specified group and its subgroups. +// Get returns a paginated list of users who are members of the specified group and its subgroups. // Users in the page are ordered by user names. // User of this resource is required to have sysadmin or admin permissions. // // Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup // // WARNING: This API only returns the first page of group members -func (s *GroupService) GetWithContext(ctx context.Context, name string) ([]GroupMember, *Response, error) { +func (s *GroupService) Get(ctx context.Context, name string) ([]GroupMember, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -80,17 +80,12 @@ func (s *GroupService) GetWithContext(ctx context.Context, name string) ([]Group return group.Members, resp, nil } -// Get wraps GetWithContext using the background context. -func (s *GroupService) Get(name string) ([]GroupMember, *Response, error) { - return s.GetWithContext(context.Background(), name) -} - -// GetWithOptionsWithContext returns a paginated list of members of the specified group and its subgroups. +// GetWithOptions returns a paginated list of members of the specified group and its subgroups. // Users in the page are ordered by user names. // User of this resource is required to have sysadmin or admin permissions. // // Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup -func (s *GroupService) GetWithOptionsWithContext(ctx context.Context, name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { +func (s *GroupService) GetWithOptions(ctx context.Context, name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { var apiEndpoint string if options == nil { apiEndpoint = fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) @@ -116,15 +111,10 @@ func (s *GroupService) GetWithOptionsWithContext(ctx context.Context, name strin return group.Members, resp, nil } -// GetWithOptions wraps GetWithOptionsWithContext using the background context. -func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { - return s.GetWithOptionsWithContext(context.Background(), name, options) -} - -// AddWithContext adds user to group +// Add adds user to group // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-addUserToGroup -func (s *GroupService) AddWithContext(ctx context.Context, groupname string, username string) (*Group, *Response, error) { +func (s *GroupService) Add(ctx context.Context, groupname string, username string) (*Group, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s", groupname) var user struct { Name string `json:"name"` @@ -145,16 +135,11 @@ func (s *GroupService) AddWithContext(ctx context.Context, groupname string, use return responseGroup, resp, nil } -// Add wraps AddWithContext using the background context. -func (s *GroupService) Add(groupname string, username string) (*Group, *Response, error) { - return s.AddWithContext(context.Background(), groupname, username) -} - -// RemoveWithContext removes user from group +// Remove removes user from group // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-removeUserFromGroup // Caller must close resp.Body -func (s *GroupService) RemoveWithContext(ctx context.Context, groupname string, username string) (*Response, error) { +func (s *GroupService) Remove(ctx context.Context, groupname string, username string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s&username=%s", groupname, username) req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { @@ -169,9 +154,3 @@ func (s *GroupService) RemoveWithContext(ctx context.Context, groupname string, return resp, nil } - -// Remove wraps RemoveWithContext using the background context. -// Caller must close resp.Body -func (s *GroupService) Remove(groupname string, username string) (*Response, error) { - return s.RemoveWithContext(context.Background(), groupname, username) -} diff --git a/onpremise/group_test.go b/onpremise/group_test.go index acdb15b..8f7e176 100644 --- a/onpremise/group_test.go +++ b/onpremise/group_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "testing" @@ -14,7 +15,7 @@ func TestGroupService_Get(t *testing.T) { testRequestURL(t, r, "/rest/api/2/group/member?groupname=default") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/group/member?includeInactiveUsers=false&maxResults=50&groupname=default&startAt=0","maxResults":50,"startAt":0,"total":2,"isLast":true,"values":[{"self":"http://www.example.com/jira/rest/api/2/user?username=michael","name":"michael","key":"michael","emailAddress":"michael@example.com","displayName":"MichaelScofield","active":true,"timeZone":"Australia/Sydney"},{"self":"http://www.example.com/jira/rest/api/2/user?username=alex","name":"alex","key":"alex","emailAddress":"alex@example.com","displayName":"AlexanderMahone","active":true,"timeZone":"Australia/Sydney"}]}`) }) - if members, _, err := testClient.Group.Get("default"); err != nil { + if members, _, err := testClient.Group.Get(context.Background(), "default"); err != nil { t.Errorf("Error given: %s", err) } else if members == nil { t.Error("Expected members. Group.Members is nil") @@ -36,7 +37,7 @@ func TestGroupService_GetPage(t *testing.T) { t.Errorf("startAt %s", startAt) } }) - if page, resp, err := testClient.Group.GetWithOptions("default", &GroupSearchOptions{ + if page, resp, err := testClient.Group.GetWithOptions(context.Background(), "default", &GroupSearchOptions{ StartAt: 0, MaxResults: 2, IncludeInactiveUsers: false, @@ -54,7 +55,7 @@ func TestGroupService_GetPage(t *testing.T) { if resp.Total != 4 { t.Errorf("Expect Result Total to be 4, but is %d", resp.Total) } - if page, resp, err := testClient.Group.GetWithOptions("default", &GroupSearchOptions{ + if page, resp, err := testClient.Group.GetWithOptions(context.Background(), "default", &GroupSearchOptions{ StartAt: 2, MaxResults: 2, IncludeInactiveUsers: false, @@ -87,7 +88,7 @@ func TestGroupService_Add(t *testing.T) { fmt.Fprint(w, `{"name":"default","self":"http://www.example.com/jira/rest/api/2/group?groupname=default","users":{"size":1,"items":[],"max-results":50,"start-index":0,"end-index":0},"expand":"users"}`) }) - if group, _, err := testClient.Group.Add("default", "theodore"); err != nil { + if group, _, err := testClient.Group.Add(context.Background(), "default", "theodore"); err != nil { t.Errorf("Error given: %s", err) } else if group == nil { t.Error("Expected group. Group is nil") @@ -105,7 +106,7 @@ func TestGroupService_Remove(t *testing.T) { fmt.Fprint(w, `{"name":"default","self":"http://www.example.com/jira/rest/api/2/group?groupname=default","users":{"size":1,"items":[],"max-results":50,"start-index":0,"end-index":0},"expand":"users"}`) }) - if _, err := testClient.Group.Remove("default", "theodore"); err != nil { + if _, err := testClient.Group.Remove(context.Background(), "default", "theodore"); err != nil { t.Errorf("Error given: %s", err) } } From 9e248123c9dcd9f4aea3ba26e5cab95a2be5502f Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 20:36:08 +0200 Subject: [PATCH 036/189] Project Service: Remove "WithContext" API methods --- cloud/project.go | 38 +++++++++----------------------------- cloud/project_test.go | 13 +++++++------ onpremise/project.go | 38 +++++++++----------------------------- onpremise/project_test.go | 13 +++++++------ 4 files changed, 32 insertions(+), 70 deletions(-) diff --git a/cloud/project.go b/cloud/project.go index 60e4eae..2e55420 100644 --- a/cloud/project.go +++ b/cloud/project.go @@ -79,23 +79,18 @@ type PermissionScheme struct { Permissions []Permission `json:"permissions" structs:"permissions,omitempty"` } -// GetListWithContext gets all projects form Jira +// GetList gets all projects form Jira // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects -func (s *ProjectService) GetListWithContext(ctx context.Context) (*ProjectList, *Response, error) { - return s.ListWithOptionsWithContext(ctx, &GetQueryOptions{}) +func (s *ProjectService) GetList(ctx context.Context) (*ProjectList, *Response, error) { + return s.ListWithOptions(ctx, &GetQueryOptions{}) } -// GetList wraps GetListWithContext using the background context. -func (s *ProjectService) GetList() (*ProjectList, *Response, error) { - return s.GetListWithContext(context.Background()) -} - -// ListWithOptionsWithContext gets all projects form Jira with optional query params, like &GetQueryOptions{Expand: "issueTypes"} to get +// ListWithOptions gets all projects form Jira with optional query params, like &GetQueryOptions{Expand: "issueTypes"} to get // a list of all projects and their supported issuetypes // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects -func (s *ProjectService) ListWithOptionsWithContext(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { +func (s *ProjectService) ListWithOptions(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { apiEndpoint := "rest/api/2/project" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -120,17 +115,12 @@ func (s *ProjectService) ListWithOptionsWithContext(ctx context.Context, options return projectList, resp, nil } -// ListWithOptions wraps ListWithOptionsWithContext using the background context. -func (s *ProjectService) ListWithOptions(options *GetQueryOptions) (*ProjectList, *Response, error) { - return s.ListWithOptionsWithContext(context.Background(), options) -} - -// GetWithContext returns a full representation of the project for the given issue key. +// Get returns a full representation of the project for the given issue key. // Jira will attempt to identify the project by the projectIdOrKey path parameter. // This can be an project id, or an project key. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject -func (s *ProjectService) GetWithContext(ctx context.Context, projectID string) (*Project, *Response, error) { +func (s *ProjectService) Get(ctx context.Context, projectID string) (*Project, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/project/%s", projectID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -147,17 +137,12 @@ func (s *ProjectService) GetWithContext(ctx context.Context, projectID string) ( return project, resp, nil } -// Get wraps GetWithContext using the background context. -func (s *ProjectService) Get(projectID string) (*Project, *Response, error) { - return s.GetWithContext(context.Background(), projectID) -} - -// GetPermissionSchemeWithContext returns a full representation of the permission scheme for the project +// GetPermissionScheme returns a full representation of the permission scheme for the project // Jira will attempt to identify the project by the projectIdOrKey path parameter. // This can be an project id, or an project key. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject -func (s *ProjectService) GetPermissionSchemeWithContext(ctx context.Context, projectID string) (*PermissionScheme, *Response, error) { +func (s *ProjectService) GetPermissionScheme(ctx context.Context, projectID string) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s/permissionscheme", projectID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -173,8 +158,3 @@ func (s *ProjectService) GetPermissionSchemeWithContext(ctx context.Context, pro return ps, resp, nil } - -// GetPermissionScheme wraps GetPermissionSchemeWithContext using the background context. -func (s *ProjectService) GetPermissionScheme(projectID string) (*PermissionScheme, *Response, error) { - return s.GetPermissionSchemeWithContext(context.Background(), projectID) -} diff --git a/cloud/project_test.go b/cloud/project_test.go index be0a4a4..8d37ff2 100644 --- a/cloud/project_test.go +++ b/cloud/project_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestProjectService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - projects, _, err := testClient.Project.GetList() + projects, _, err := testClient.Project.GetList(context.Background()) if projects == nil { t.Error("Expected project list. Project list is nil") } @@ -46,7 +47,7 @@ func TestProjectService_ListWithOptions(t *testing.T) { fmt.Fprint(w, string(raw)) }) - projects, _, err := testClient.Project.ListWithOptions(&GetQueryOptions{Expand: "issueTypes"}) + projects, _, err := testClient.Project.ListWithOptions(context.Background(), &GetQueryOptions{Expand: "issueTypes"}) if projects == nil { t.Error("Expected project list. Project list is nil") } @@ -70,7 +71,7 @@ func TestProjectService_Get(t *testing.T) { fmt.Fprint(w, string(raw)) }) - projects, _, err := testClient.Project.Get("12310505") + projects, _, err := testClient.Project.Get(context.Background(), "12310505") if err != nil { t.Errorf("Error given: %s", err) } @@ -94,7 +95,7 @@ func TestProjectService_Get_NoProject(t *testing.T) { fmt.Fprint(w, nil) }) - projects, resp, err := testClient.Project.Get("99999999") + projects, resp, err := testClient.Project.Get(context.Background(), "99999999") if projects != nil { t.Errorf("Expected nil. Got %+v", projects) } @@ -118,7 +119,7 @@ func TestProjectService_GetPermissionScheme_Failure(t *testing.T) { fmt.Fprint(w, nil) }) - permissionScheme, resp, err := testClient.Project.GetPermissionScheme("99999999") + permissionScheme, resp, err := testClient.Project.GetPermissionScheme(context.Background(), "99999999") if permissionScheme != nil { t.Errorf("Expected nil. Got %+v", permissionScheme) } @@ -148,7 +149,7 @@ func TestProjectService_GetPermissionScheme_Success(t *testing.T) { }`) }) - permissionScheme, resp, err := testClient.Project.GetPermissionScheme("99999999") + permissionScheme, resp, err := testClient.Project.GetPermissionScheme(context.Background(), "99999999") if permissionScheme.ID != 10201 { t.Errorf("Expected Permission Scheme ID. Got %+v", permissionScheme) } diff --git a/onpremise/project.go b/onpremise/project.go index 6b6e8d7..b9d72e1 100644 --- a/onpremise/project.go +++ b/onpremise/project.go @@ -79,23 +79,18 @@ type PermissionScheme struct { Permissions []Permission `json:"permissions" structs:"permissions,omitempty"` } -// GetListWithContext gets all projects form Jira +// GetList gets all projects form Jira // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects -func (s *ProjectService) GetListWithContext(ctx context.Context) (*ProjectList, *Response, error) { - return s.ListWithOptionsWithContext(ctx, &GetQueryOptions{}) +func (s *ProjectService) GetList(ctx context.Context) (*ProjectList, *Response, error) { + return s.ListWithOptions(ctx, &GetQueryOptions{}) } -// GetList wraps GetListWithContext using the background context. -func (s *ProjectService) GetList() (*ProjectList, *Response, error) { - return s.GetListWithContext(context.Background()) -} - -// ListWithOptionsWithContext gets all projects form Jira with optional query params, like &GetQueryOptions{Expand: "issueTypes"} to get +// ListWithOptions gets all projects form Jira with optional query params, like &GetQueryOptions{Expand: "issueTypes"} to get // a list of all projects and their supported issuetypes // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects -func (s *ProjectService) ListWithOptionsWithContext(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { +func (s *ProjectService) ListWithOptions(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { apiEndpoint := "rest/api/2/project" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -120,17 +115,12 @@ func (s *ProjectService) ListWithOptionsWithContext(ctx context.Context, options return projectList, resp, nil } -// ListWithOptions wraps ListWithOptionsWithContext using the background context. -func (s *ProjectService) ListWithOptions(options *GetQueryOptions) (*ProjectList, *Response, error) { - return s.ListWithOptionsWithContext(context.Background(), options) -} - -// GetWithContext returns a full representation of the project for the given issue key. +// Get returns a full representation of the project for the given issue key. // Jira will attempt to identify the project by the projectIdOrKey path parameter. // This can be an project id, or an project key. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject -func (s *ProjectService) GetWithContext(ctx context.Context, projectID string) (*Project, *Response, error) { +func (s *ProjectService) Get(ctx context.Context, projectID string) (*Project, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/project/%s", projectID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -147,17 +137,12 @@ func (s *ProjectService) GetWithContext(ctx context.Context, projectID string) ( return project, resp, nil } -// Get wraps GetWithContext using the background context. -func (s *ProjectService) Get(projectID string) (*Project, *Response, error) { - return s.GetWithContext(context.Background(), projectID) -} - -// GetPermissionSchemeWithContext returns a full representation of the permission scheme for the project +// GetPermissionScheme returns a full representation of the permission scheme for the project // Jira will attempt to identify the project by the projectIdOrKey path parameter. // This can be an project id, or an project key. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject -func (s *ProjectService) GetPermissionSchemeWithContext(ctx context.Context, projectID string) (*PermissionScheme, *Response, error) { +func (s *ProjectService) GetPermissionScheme(ctx context.Context, projectID string) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s/permissionscheme", projectID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -173,8 +158,3 @@ func (s *ProjectService) GetPermissionSchemeWithContext(ctx context.Context, pro return ps, resp, nil } - -// GetPermissionScheme wraps GetPermissionSchemeWithContext using the background context. -func (s *ProjectService) GetPermissionScheme(projectID string) (*PermissionScheme, *Response, error) { - return s.GetPermissionSchemeWithContext(context.Background(), projectID) -} diff --git a/onpremise/project_test.go b/onpremise/project_test.go index 21e583a..655222f 100644 --- a/onpremise/project_test.go +++ b/onpremise/project_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestProjectService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - projects, _, err := testClient.Project.GetList() + projects, _, err := testClient.Project.GetList(context.Background()) if projects == nil { t.Error("Expected project list. Project list is nil") } @@ -46,7 +47,7 @@ func TestProjectService_ListWithOptions(t *testing.T) { fmt.Fprint(w, string(raw)) }) - projects, _, err := testClient.Project.ListWithOptions(&GetQueryOptions{Expand: "issueTypes"}) + projects, _, err := testClient.Project.ListWithOptions(context.Background(), &GetQueryOptions{Expand: "issueTypes"}) if projects == nil { t.Error("Expected project list. Project list is nil") } @@ -70,7 +71,7 @@ func TestProjectService_Get(t *testing.T) { fmt.Fprint(w, string(raw)) }) - projects, _, err := testClient.Project.Get("12310505") + projects, _, err := testClient.Project.Get(context.Background(), "12310505") if err != nil { t.Errorf("Error given: %s", err) } @@ -94,7 +95,7 @@ func TestProjectService_Get_NoProject(t *testing.T) { fmt.Fprint(w, nil) }) - projects, resp, err := testClient.Project.Get("99999999") + projects, resp, err := testClient.Project.Get(context.Background(), "99999999") if projects != nil { t.Errorf("Expected nil. Got %+v", projects) } @@ -118,7 +119,7 @@ func TestProjectService_GetPermissionScheme_Failure(t *testing.T) { fmt.Fprint(w, nil) }) - permissionScheme, resp, err := testClient.Project.GetPermissionScheme("99999999") + permissionScheme, resp, err := testClient.Project.GetPermissionScheme(context.Background(), "99999999") if permissionScheme != nil { t.Errorf("Expected nil. Got %+v", permissionScheme) } @@ -148,7 +149,7 @@ func TestProjectService_GetPermissionScheme_Success(t *testing.T) { }`) }) - permissionScheme, resp, err := testClient.Project.GetPermissionScheme("99999999") + permissionScheme, resp, err := testClient.Project.GetPermissionScheme(context.Background(), "99999999") if permissionScheme.ID != 10201 { t.Errorf("Expected Permission Scheme ID. Got %+v", permissionScheme) } From 770cb36ed55bd55f6518dceae0dbf7049b329b44 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 21:05:40 +0200 Subject: [PATCH 037/189] Organization Service: Remove "WithContext" API methods --- cloud/organization.go | 104 +++++++-------------------------- cloud/organization_test.go | 25 ++++---- onpremise/organization.go | 104 +++++++-------------------------- onpremise/organization_test.go | 25 ++++---- 4 files changed, 70 insertions(+), 188 deletions(-) diff --git a/cloud/organization.go b/cloud/organization.go index 515f6c2..a40b9a6 100644 --- a/cloud/organization.go +++ b/cloud/organization.go @@ -53,14 +53,14 @@ type PropertyKeys struct { Keys []PropertyKey `json:"keys,omitempty" structs:"keys,omitempty"` } -// GetAllOrganizationsWithContext returns a list of organizations in +// GetAllOrganizations returns a list of organizations in // the Jira Service Management instance. // Use this method when you want to present a list // of organizations or want to locate an organization // by name. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-group-organization -func (s *OrganizationService) GetAllOrganizationsWithContext(ctx context.Context, start int, limit int, accountID string) (*PagedDTO, *Response, error) { +func (s *OrganizationService) GetAllOrganizations(ctx context.Context, start int, limit int, accountID string) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization?start=%d&limit=%d", start, limit) if accountID != "" { apiEndPoint += fmt.Sprintf("&accountId=%s", accountID) @@ -83,16 +83,11 @@ func (s *OrganizationService) GetAllOrganizationsWithContext(ctx context.Context return v, resp, nil } -// GetAllOrganizations wraps GetAllOrganizationsWithContext using the background context. -func (s *OrganizationService) GetAllOrganizations(start int, limit int, accountID string) (*PagedDTO, *Response, error) { - return s.GetAllOrganizationsWithContext(context.Background(), start, limit, accountID) -} - -// CreateOrganizationWithContext creates an organization by +// CreateOrganization creates an organization by // passing the name of the organization. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-post -func (s *OrganizationService) CreateOrganizationWithContext(ctx context.Context, name string) (*Organization, *Response, error) { +func (s *OrganizationService) CreateOrganization(ctx context.Context, name string) (*Organization, *Response, error) { apiEndPoint := "rest/servicedeskapi/organization" organization := OrganizationCreationDTO{ @@ -116,19 +111,14 @@ func (s *OrganizationService) CreateOrganizationWithContext(ctx context.Context, return o, resp, nil } -// CreateOrganization wraps CreateOrganizationWithContext using the background context. -func (s *OrganizationService) CreateOrganization(name string) (*Organization, *Response, error) { - return s.CreateOrganizationWithContext(context.Background(), name) -} - -// GetOrganizationWithContext returns details of an +// GetOrganization returns details of an // organization. Use this method to get organization // details whenever your application component is // passed an organization ID but needs to display // other organization details. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-get -func (s *OrganizationService) GetOrganizationWithContext(ctx context.Context, organizationID int) (*Organization, *Response, error) { +func (s *OrganizationService) GetOrganization(ctx context.Context, organizationID int) (*Organization, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) @@ -148,19 +138,14 @@ func (s *OrganizationService) GetOrganizationWithContext(ctx context.Context, or return o, resp, nil } -// GetOrganization wraps GetOrganizationWithContext using the background context. -func (s *OrganizationService) GetOrganization(organizationID int) (*Organization, *Response, error) { - return s.GetOrganizationWithContext(context.Background(), organizationID) -} - -// DeleteOrganizationWithContext deletes an organization. Note that +// DeleteOrganization deletes an organization. Note that // the organization is deleted regardless // of other associations it may have. // For example, associations with service desks. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-delete // Caller must close resp.Body -func (s *OrganizationService) DeleteOrganizationWithContext(ctx context.Context, organizationID int) (*Response, error) { +func (s *OrganizationService) DeleteOrganization(ctx context.Context, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) @@ -178,19 +163,13 @@ func (s *OrganizationService) DeleteOrganizationWithContext(ctx context.Context, return resp, nil } -// DeleteOrganization wraps DeleteOrganizationWithContext using the background context. -// Caller must close resp.Body -func (s *OrganizationService) DeleteOrganization(organizationID int) (*Response, error) { - return s.DeleteOrganizationWithContext(context.Background(), organizationID) -} - -// GetPropertiesKeysWithContext returns the keys of +// GetPropertiesKeys returns the keys of // all properties for an organization. Use this resource // when you need to find out what additional properties // items have been added to an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-get -func (s *OrganizationService) GetPropertiesKeysWithContext(ctx context.Context, organizationID int) (*PropertyKeys, *Response, error) { +func (s *OrganizationService) GetPropertiesKeys(ctx context.Context, organizationID int) (*PropertyKeys, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property", organizationID) req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) @@ -210,17 +189,12 @@ func (s *OrganizationService) GetPropertiesKeysWithContext(ctx context.Context, return pk, resp, nil } -// GetPropertiesKeys wraps GetPropertiesKeysWithContext using the background context. -func (s *OrganizationService) GetPropertiesKeys(organizationID int) (*PropertyKeys, *Response, error) { - return s.GetPropertiesKeysWithContext(context.Background(), organizationID) -} - -// GetPropertyWithContext returns the value of a property +// GetProperty returns the value of a property // from an organization. Use this method to obtain the JSON // content for an organization's property. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-get -func (s *OrganizationService) GetPropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*EntityProperty, *Response, error) { +func (s *OrganizationService) GetProperty(ctx context.Context, organizationID int, propertyKey string) (*EntityProperty, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) @@ -240,18 +214,13 @@ func (s *OrganizationService) GetPropertyWithContext(ctx context.Context, organi return ep, resp, nil } -// GetProperty wraps GetPropertyWithContext using the background context. -func (s *OrganizationService) GetProperty(organizationID int, propertyKey string) (*EntityProperty, *Response, error) { - return s.GetPropertyWithContext(context.Background(), organizationID, propertyKey) -} - -// SetPropertyWithContext sets the value of a +// SetProperty sets the value of a // property for an organization. Use this // resource to store custom data against an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-put // Caller must close resp.Body -func (s *OrganizationService) SetPropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { +func (s *OrganizationService) SetProperty(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) req, err := s.client.NewRequest(ctx, "PUT", apiEndPoint, nil) @@ -270,17 +239,11 @@ func (s *OrganizationService) SetPropertyWithContext(ctx context.Context, organi return resp, nil } -// SetProperty wraps SetPropertyWithContext using the background context. -// Caller must close resp.Body -func (s *OrganizationService) SetProperty(organizationID int, propertyKey string) (*Response, error) { - return s.SetPropertyWithContext(context.Background(), organizationID, propertyKey) -} - -// DeletePropertyWithContext removes a property from an organization. +// DeleteProperty removes a property from an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-delete // Caller must close resp.Body -func (s *OrganizationService) DeletePropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { +func (s *OrganizationService) DeleteProperty(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) @@ -299,20 +262,14 @@ func (s *OrganizationService) DeletePropertyWithContext(ctx context.Context, org return resp, nil } -// DeleteProperty wraps DeletePropertyWithContext using the background context. -// Caller must close resp.Body -func (s *OrganizationService) DeleteProperty(organizationID int, propertyKey string) (*Response, error) { - return s.DeletePropertyWithContext(context.Background(), organizationID, propertyKey) -} - -// GetUsersWithContext returns all the users +// GetUsers returns all the users // associated with an organization. Use this // method where you want to provide a list of // users for an organization or determine if // a user is associated with an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-get -func (s *OrganizationService) GetUsersWithContext(ctx context.Context, organizationID int, start int, limit int) (*PagedDTO, *Response, error) { +func (s *OrganizationService) GetUsers(ctx context.Context, organizationID int, start int, limit int) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user?start=%d&limit=%d", organizationID, start, limit) req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) @@ -332,16 +289,11 @@ func (s *OrganizationService) GetUsersWithContext(ctx context.Context, organizat return users, resp, nil } -// GetUsers wraps GetUsersWithContext using the background context. -func (s *OrganizationService) GetUsers(organizationID int, start int, limit int) (*PagedDTO, *Response, error) { - return s.GetUsersWithContext(context.Background(), organizationID, start, limit) -} - -// AddUsersWithContext adds users to an organization. +// AddUsers adds users to an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-post // Caller must close resp.Body -func (s *OrganizationService) AddUsersWithContext(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { +func (s *OrganizationService) AddUsers(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, users) @@ -359,17 +311,11 @@ func (s *OrganizationService) AddUsersWithContext(ctx context.Context, organizat return resp, nil } -// AddUsers wraps AddUsersWithContext using the background context. -// Caller must close resp.Body -func (s *OrganizationService) AddUsers(organizationID int, users OrganizationUsersDTO) (*Response, error) { - return s.AddUsersWithContext(context.Background(), organizationID, users) -} - -// RemoveUsersWithContext removes users from an organization. +// RemoveUsers removes users from an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-delete // Caller must close resp.Body -func (s *OrganizationService) RemoveUsersWithContext(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { +func (s *OrganizationService) RemoveUsers(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) @@ -387,9 +333,3 @@ func (s *OrganizationService) RemoveUsersWithContext(ctx context.Context, organi return resp, nil } - -// RemoveUsers wraps RemoveUsersWithContext using the background context. -// Caller must close resp.Body -func (s *OrganizationService) RemoveUsers(organizationID int, users OrganizationUsersDTO) (*Response, error) { - return s.RemoveUsersWithContext(context.Background(), organizationID, users) -} diff --git a/cloud/organization_test.go b/cloud/organization_test.go index ad96d12..98581df 100644 --- a/cloud/organization_test.go +++ b/cloud/organization_test.go @@ -1,13 +1,14 @@ package cloud import ( + "context" "encoding/json" "fmt" "net/http" "testing" ) -func TestOrganizationService_GetAllOrganizationsWithContext(t *testing.T) { +func TestOrganizationService_GetAllOrganizations(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization", func(w http.ResponseWriter, r *http.Request) { @@ -18,7 +19,7 @@ func TestOrganizationService_GetAllOrganizationsWithContext(t *testing.T) { fmt.Fprint(w, `{ "_expands": [], "size": 1, "start": 1, "limit": 1, "isLastPage": false, "_links": { "base": "https://your-domain.atlassian.net/rest/servicedeskapi", "context": "context", "next": "https://your-domain.atlassian.net/rest/servicedeskapi/organization?start=2&limit=1", "prev": "https://your-domain.atlassian.net/rest/servicedeskapi/organization?start=0&limit=1" }, "values": [ { "id": "1", "name": "Charlie Cakes Franchises", "_links": { "self": "https://your-domain.atlassian.net/rest/servicedeskapi/organization/1" } } ] }`) }) - result, _, err := testClient.Organization.GetAllOrganizations(0, 50, "") + result, _, err := testClient.Organization.GetAllOrganizations(context.Background(), 0, 50, "") if result == nil { t.Error("Expected Organizations. Result is nil") @@ -45,7 +46,7 @@ func TestOrganizationService_CreateOrganization(t *testing.T) { }) name := "MyOrg" - o, _, err := testClient.Organization.CreateOrganization(name) + o, _, err := testClient.Organization.CreateOrganization(context.Background(), name) if o == nil { t.Error("Expected Organization. Result is nil") @@ -70,7 +71,7 @@ func TestOrganizationService_GetOrganization(t *testing.T) { }) id := 1 - o, _, err := testClient.Organization.GetOrganization(id) + o, _, err := testClient.Organization.GetOrganization(context.Background(), id) if err != nil { t.Errorf("Error given: %s", err) @@ -93,7 +94,7 @@ func TestOrganizationService_DeleteOrganization(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - _, err := testClient.Organization.DeleteOrganization(1) + _, err := testClient.Organization.DeleteOrganization(context.Background(), 1) if err != nil { t.Errorf("Error given: %s", err) @@ -118,7 +119,7 @@ func TestOrganizationService_GetPropertiesKeys(t *testing.T) { }`) }) - pk, _, err := testClient.Organization.GetPropertiesKeys(1) + pk, _, err := testClient.Organization.GetPropertiesKeys(context.Background(), 1) if err != nil { t.Errorf("Error given: %s", err) @@ -149,7 +150,7 @@ func TestOrganizationService_GetProperty(t *testing.T) { }) key := "organization.attributes" - ep, _, err := testClient.Organization.GetProperty(1, key) + ep, _, err := testClient.Organization.GetProperty(context.Background(), 1, key) if err != nil { t.Errorf("Error given: %s", err) @@ -173,7 +174,7 @@ func TestOrganizationService_SetProperty(t *testing.T) { }) key := "organization.attributes" - _, err := testClient.Organization.SetProperty(1, key) + _, err := testClient.Organization.SetProperty(context.Background(), 1, key) if err != nil { t.Errorf("Error given: %s", err) @@ -191,7 +192,7 @@ func TestOrganizationService_DeleteProperty(t *testing.T) { }) key := "organization.attributes" - _, err := testClient.Organization.DeleteProperty(1, key) + _, err := testClient.Organization.DeleteProperty(context.Background(), 1, key) if err != nil { t.Errorf("Error given: %s", err) @@ -261,7 +262,7 @@ func TestOrganizationService_GetUsers(t *testing.T) { }`) }) - users, _, err := testClient.Organization.GetUsers(1, 0, 50) + users, _, err := testClient.Organization.GetUsers(context.Background(), 1, 0, 50) if err != nil { t.Errorf("Error given: %s", err) @@ -294,7 +295,7 @@ func TestOrganizationService_AddUsers(t *testing.T) { "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3a01db05e2a66fa80bd", }, } - _, err := testClient.Organization.AddUsers(1, users) + _, err := testClient.Organization.AddUsers(context.Background(), 1, users) if err != nil { t.Errorf("Error given: %s", err) @@ -317,7 +318,7 @@ func TestOrganizationService_RemoveUsers(t *testing.T) { "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3a01db05e2a66fa80bd", }, } - _, err := testClient.Organization.RemoveUsers(1, users) + _, err := testClient.Organization.RemoveUsers(context.Background(), 1, users) if err != nil { t.Errorf("Error given: %s", err) diff --git a/onpremise/organization.go b/onpremise/organization.go index 7a752d5..dafa7a7 100644 --- a/onpremise/organization.go +++ b/onpremise/organization.go @@ -53,14 +53,14 @@ type PropertyKeys struct { Keys []PropertyKey `json:"keys,omitempty" structs:"keys,omitempty"` } -// GetAllOrganizationsWithContext returns a list of organizations in +// GetAllOrganizations returns a list of organizations in // the Jira Service Management instance. // Use this method when you want to present a list // of organizations or want to locate an organization // by name. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-group-organization -func (s *OrganizationService) GetAllOrganizationsWithContext(ctx context.Context, start int, limit int, accountID string) (*PagedDTO, *Response, error) { +func (s *OrganizationService) GetAllOrganizations(ctx context.Context, start int, limit int, accountID string) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization?start=%d&limit=%d", start, limit) if accountID != "" { apiEndPoint += fmt.Sprintf("&accountId=%s", accountID) @@ -83,16 +83,11 @@ func (s *OrganizationService) GetAllOrganizationsWithContext(ctx context.Context return v, resp, nil } -// GetAllOrganizations wraps GetAllOrganizationsWithContext using the background context. -func (s *OrganizationService) GetAllOrganizations(start int, limit int, accountID string) (*PagedDTO, *Response, error) { - return s.GetAllOrganizationsWithContext(context.Background(), start, limit, accountID) -} - -// CreateOrganizationWithContext creates an organization by +// CreateOrganization creates an organization by // passing the name of the organization. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-post -func (s *OrganizationService) CreateOrganizationWithContext(ctx context.Context, name string) (*Organization, *Response, error) { +func (s *OrganizationService) CreateOrganization(ctx context.Context, name string) (*Organization, *Response, error) { apiEndPoint := "rest/servicedeskapi/organization" organization := OrganizationCreationDTO{ @@ -116,19 +111,14 @@ func (s *OrganizationService) CreateOrganizationWithContext(ctx context.Context, return o, resp, nil } -// CreateOrganization wraps CreateOrganizationWithContext using the background context. -func (s *OrganizationService) CreateOrganization(name string) (*Organization, *Response, error) { - return s.CreateOrganizationWithContext(context.Background(), name) -} - -// GetOrganizationWithContext returns details of an +// GetOrganization returns details of an // organization. Use this method to get organization // details whenever your application component is // passed an organization ID but needs to display // other organization details. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-get -func (s *OrganizationService) GetOrganizationWithContext(ctx context.Context, organizationID int) (*Organization, *Response, error) { +func (s *OrganizationService) GetOrganization(ctx context.Context, organizationID int) (*Organization, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) @@ -148,19 +138,14 @@ func (s *OrganizationService) GetOrganizationWithContext(ctx context.Context, or return o, resp, nil } -// GetOrganization wraps GetOrganizationWithContext using the background context. -func (s *OrganizationService) GetOrganization(organizationID int) (*Organization, *Response, error) { - return s.GetOrganizationWithContext(context.Background(), organizationID) -} - -// DeleteOrganizationWithContext deletes an organization. Note that +// DeleteOrganization deletes an organization. Note that // the organization is deleted regardless // of other associations it may have. // For example, associations with service desks. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-delete // Caller must close resp.Body -func (s *OrganizationService) DeleteOrganizationWithContext(ctx context.Context, organizationID int) (*Response, error) { +func (s *OrganizationService) DeleteOrganization(ctx context.Context, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) @@ -178,19 +163,13 @@ func (s *OrganizationService) DeleteOrganizationWithContext(ctx context.Context, return resp, nil } -// DeleteOrganization wraps DeleteOrganizationWithContext using the background context. -// Caller must close resp.Body -func (s *OrganizationService) DeleteOrganization(organizationID int) (*Response, error) { - return s.DeleteOrganizationWithContext(context.Background(), organizationID) -} - -// GetPropertiesKeysWithContext returns the keys of +// GetPropertiesKeys returns the keys of // all properties for an organization. Use this resource // when you need to find out what additional properties // items have been added to an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-get -func (s *OrganizationService) GetPropertiesKeysWithContext(ctx context.Context, organizationID int) (*PropertyKeys, *Response, error) { +func (s *OrganizationService) GetPropertiesKeys(ctx context.Context, organizationID int) (*PropertyKeys, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property", organizationID) req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) @@ -210,17 +189,12 @@ func (s *OrganizationService) GetPropertiesKeysWithContext(ctx context.Context, return pk, resp, nil } -// GetPropertiesKeys wraps GetPropertiesKeysWithContext using the background context. -func (s *OrganizationService) GetPropertiesKeys(organizationID int) (*PropertyKeys, *Response, error) { - return s.GetPropertiesKeysWithContext(context.Background(), organizationID) -} - -// GetPropertyWithContext returns the value of a property +// GetProperty returns the value of a property // from an organization. Use this method to obtain the JSON // content for an organization's property. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-get -func (s *OrganizationService) GetPropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*EntityProperty, *Response, error) { +func (s *OrganizationService) GetProperty(ctx context.Context, organizationID int, propertyKey string) (*EntityProperty, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) @@ -240,18 +214,13 @@ func (s *OrganizationService) GetPropertyWithContext(ctx context.Context, organi return ep, resp, nil } -// GetProperty wraps GetPropertyWithContext using the background context. -func (s *OrganizationService) GetProperty(organizationID int, propertyKey string) (*EntityProperty, *Response, error) { - return s.GetPropertyWithContext(context.Background(), organizationID, propertyKey) -} - -// SetPropertyWithContext sets the value of a +// SetProperty sets the value of a // property for an organization. Use this // resource to store custom data against an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-put // Caller must close resp.Body -func (s *OrganizationService) SetPropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { +func (s *OrganizationService) SetProperty(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) req, err := s.client.NewRequest(ctx, "PUT", apiEndPoint, nil) @@ -270,17 +239,11 @@ func (s *OrganizationService) SetPropertyWithContext(ctx context.Context, organi return resp, nil } -// SetProperty wraps SetPropertyWithContext using the background context. -// Caller must close resp.Body -func (s *OrganizationService) SetProperty(organizationID int, propertyKey string) (*Response, error) { - return s.SetPropertyWithContext(context.Background(), organizationID, propertyKey) -} - -// DeletePropertyWithContext removes a property from an organization. +// DeleteProperty removes a property from an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-delete // Caller must close resp.Body -func (s *OrganizationService) DeletePropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { +func (s *OrganizationService) DeleteProperty(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) @@ -299,20 +262,14 @@ func (s *OrganizationService) DeletePropertyWithContext(ctx context.Context, org return resp, nil } -// DeleteProperty wraps DeletePropertyWithContext using the background context. -// Caller must close resp.Body -func (s *OrganizationService) DeleteProperty(organizationID int, propertyKey string) (*Response, error) { - return s.DeletePropertyWithContext(context.Background(), organizationID, propertyKey) -} - -// GetUsersWithContext returns all the users +// GetUsers returns all the users // associated with an organization. Use this // method where you want to provide a list of // users for an organization or determine if // a user is associated with an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-get -func (s *OrganizationService) GetUsersWithContext(ctx context.Context, organizationID int, start int, limit int) (*PagedDTO, *Response, error) { +func (s *OrganizationService) GetUsers(ctx context.Context, organizationID int, start int, limit int) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user?start=%d&limit=%d", organizationID, start, limit) req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) @@ -332,16 +289,11 @@ func (s *OrganizationService) GetUsersWithContext(ctx context.Context, organizat return users, resp, nil } -// GetUsers wraps GetUsersWithContext using the background context. -func (s *OrganizationService) GetUsers(organizationID int, start int, limit int) (*PagedDTO, *Response, error) { - return s.GetUsersWithContext(context.Background(), organizationID, start, limit) -} - -// AddUsersWithContext adds users to an organization. +// AddUsers adds users to an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-post // Caller must close resp.Body -func (s *OrganizationService) AddUsersWithContext(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { +func (s *OrganizationService) AddUsers(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, users) @@ -359,17 +311,11 @@ func (s *OrganizationService) AddUsersWithContext(ctx context.Context, organizat return resp, nil } -// AddUsers wraps AddUsersWithContext using the background context. -// Caller must close resp.Body -func (s *OrganizationService) AddUsers(organizationID int, users OrganizationUsersDTO) (*Response, error) { - return s.AddUsersWithContext(context.Background(), organizationID, users) -} - -// RemoveUsersWithContext removes users from an organization. +// RemoveUsers removes users from an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-delete // Caller must close resp.Body -func (s *OrganizationService) RemoveUsersWithContext(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { +func (s *OrganizationService) RemoveUsers(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) @@ -387,9 +333,3 @@ func (s *OrganizationService) RemoveUsersWithContext(ctx context.Context, organi return resp, nil } - -// RemoveUsers wraps RemoveUsersWithContext using the background context. -// Caller must close resp.Body -func (s *OrganizationService) RemoveUsers(organizationID int, users OrganizationUsersDTO) (*Response, error) { - return s.RemoveUsersWithContext(context.Background(), organizationID, users) -} diff --git a/onpremise/organization_test.go b/onpremise/organization_test.go index 809d9ee..fa72e72 100644 --- a/onpremise/organization_test.go +++ b/onpremise/organization_test.go @@ -1,13 +1,14 @@ package onpremise import ( + "context" "encoding/json" "fmt" "net/http" "testing" ) -func TestOrganizationService_GetAllOrganizationsWithContext(t *testing.T) { +func TestOrganizationService_GetAllOrganizations(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization", func(w http.ResponseWriter, r *http.Request) { @@ -18,7 +19,7 @@ func TestOrganizationService_GetAllOrganizationsWithContext(t *testing.T) { fmt.Fprint(w, `{ "_expands": [], "size": 1, "start": 1, "limit": 1, "isLastPage": false, "_links": { "base": "https://your-domain.atlassian.net/rest/servicedeskapi", "context": "context", "next": "https://your-domain.atlassian.net/rest/servicedeskapi/organization?start=2&limit=1", "prev": "https://your-domain.atlassian.net/rest/servicedeskapi/organization?start=0&limit=1" }, "values": [ { "id": "1", "name": "Charlie Cakes Franchises", "_links": { "self": "https://your-domain.atlassian.net/rest/servicedeskapi/organization/1" } } ] }`) }) - result, _, err := testClient.Organization.GetAllOrganizations(0, 50, "") + result, _, err := testClient.Organization.GetAllOrganizations(context.Background(), 0, 50, "") if result == nil { t.Error("Expected Organizations. Result is nil") @@ -45,7 +46,7 @@ func TestOrganizationService_CreateOrganization(t *testing.T) { }) name := "MyOrg" - o, _, err := testClient.Organization.CreateOrganization(name) + o, _, err := testClient.Organization.CreateOrganization(context.Background(), name) if o == nil { t.Error("Expected Organization. Result is nil") @@ -70,7 +71,7 @@ func TestOrganizationService_GetOrganization(t *testing.T) { }) id := 1 - o, _, err := testClient.Organization.GetOrganization(id) + o, _, err := testClient.Organization.GetOrganization(context.Background(), id) if err != nil { t.Errorf("Error given: %s", err) @@ -93,7 +94,7 @@ func TestOrganizationService_DeleteOrganization(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - _, err := testClient.Organization.DeleteOrganization(1) + _, err := testClient.Organization.DeleteOrganization(context.Background(), 1) if err != nil { t.Errorf("Error given: %s", err) @@ -118,7 +119,7 @@ func TestOrganizationService_GetPropertiesKeys(t *testing.T) { }`) }) - pk, _, err := testClient.Organization.GetPropertiesKeys(1) + pk, _, err := testClient.Organization.GetPropertiesKeys(context.Background(), 1) if err != nil { t.Errorf("Error given: %s", err) @@ -149,7 +150,7 @@ func TestOrganizationService_GetProperty(t *testing.T) { }) key := "organization.attributes" - ep, _, err := testClient.Organization.GetProperty(1, key) + ep, _, err := testClient.Organization.GetProperty(context.Background(), 1, key) if err != nil { t.Errorf("Error given: %s", err) @@ -173,7 +174,7 @@ func TestOrganizationService_SetProperty(t *testing.T) { }) key := "organization.attributes" - _, err := testClient.Organization.SetProperty(1, key) + _, err := testClient.Organization.SetProperty(context.Background(), 1, key) if err != nil { t.Errorf("Error given: %s", err) @@ -191,7 +192,7 @@ func TestOrganizationService_DeleteProperty(t *testing.T) { }) key := "organization.attributes" - _, err := testClient.Organization.DeleteProperty(1, key) + _, err := testClient.Organization.DeleteProperty(context.Background(), 1, key) if err != nil { t.Errorf("Error given: %s", err) @@ -261,7 +262,7 @@ func TestOrganizationService_GetUsers(t *testing.T) { }`) }) - users, _, err := testClient.Organization.GetUsers(1, 0, 50) + users, _, err := testClient.Organization.GetUsers(context.Background(), 1, 0, 50) if err != nil { t.Errorf("Error given: %s", err) @@ -294,7 +295,7 @@ func TestOrganizationService_AddUsers(t *testing.T) { "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3a01db05e2a66fa80bd", }, } - _, err := testClient.Organization.AddUsers(1, users) + _, err := testClient.Organization.AddUsers(context.Background(), 1, users) if err != nil { t.Errorf("Error given: %s", err) @@ -317,7 +318,7 @@ func TestOrganizationService_RemoveUsers(t *testing.T) { "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3a01db05e2a66fa80bd", }, } - _, err := testClient.Organization.RemoveUsers(1, users) + _, err := testClient.Organization.RemoveUsers(context.Background(), 1, users) if err != nil { t.Errorf("Error given: %s", err) From ef937e6d2899d86349a5b199e34d29922200625f Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 21:06:11 +0200 Subject: [PATCH 038/189] Issuelink Service: Remove "WithContext" API methods --- cloud/issuelinktype.go | 47 +++++++-------------------------- cloud/issuelinktype_test.go | 11 ++++---- onpremise/issuelinktype.go | 47 +++++++-------------------------- onpremise/issuelinktype_test.go | 11 ++++---- 4 files changed, 32 insertions(+), 84 deletions(-) diff --git a/cloud/issuelinktype.go b/cloud/issuelinktype.go index 9d09eea..0ab8b03 100644 --- a/cloud/issuelinktype.go +++ b/cloud/issuelinktype.go @@ -12,10 +12,10 @@ import ( // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Issue-link-types type IssueLinkTypeService service -// GetListWithContext gets all of the issue link types from Jira. +// GetList gets all of the issue link types from Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-get -func (s *IssueLinkTypeService) GetListWithContext(ctx context.Context) ([]IssueLinkType, *Response, error) { +func (s *IssueLinkTypeService) GetList(ctx context.Context) ([]IssueLinkType, *Response, error) { apiEndpoint := "rest/api/2/issueLinkType" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -30,15 +30,10 @@ func (s *IssueLinkTypeService) GetListWithContext(ctx context.Context) ([]IssueL return linkTypeList, resp, nil } -// GetList wraps GetListWithContext using the background context. -func (s *IssueLinkTypeService) GetList() ([]IssueLinkType, *Response, error) { - return s.GetListWithContext(context.Background()) -} - -// GetWithContext gets info of a specific issue link type from Jira. +// Get gets info of a specific issue link type from Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-get -func (s *IssueLinkTypeService) GetWithContext(ctx context.Context, ID string) (*IssueLinkType, *Response, error) { +func (s *IssueLinkTypeService) Get(ctx context.Context, ID string) (*IssueLinkType, *Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) if err != nil { @@ -53,15 +48,10 @@ func (s *IssueLinkTypeService) GetWithContext(ctx context.Context, ID string) (* return linkType, resp, nil } -// Get wraps GetWithContext using the background context. -func (s *IssueLinkTypeService) Get(ID string) (*IssueLinkType, *Response, error) { - return s.GetWithContext(context.Background(), ID) -} - -// CreateWithContext creates an issue link type in Jira. +// Create creates an issue link type in Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-post -func (s *IssueLinkTypeService) CreateWithContext(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { +func (s *IssueLinkTypeService) Create(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := "/rest/api/2/issueLinkType" req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, linkType) if err != nil { @@ -88,16 +78,11 @@ func (s *IssueLinkTypeService) CreateWithContext(ctx context.Context, linkType * return linkType, resp, nil } -// Create wraps CreateWithContext using the background context. -func (s *IssueLinkTypeService) Create(linkType *IssueLinkType) (*IssueLinkType, *Response, error) { - return s.CreateWithContext(context.Background(), linkType) -} - -// UpdateWithContext updates an issue link type. The issue is found by key. +// Update updates an issue link type. The issue is found by key. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-put // Caller must close resp.Body -func (s *IssueLinkTypeService) UpdateWithContext(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { +func (s *IssueLinkTypeService) Update(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", linkType.ID) req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, linkType) if err != nil { @@ -111,17 +96,11 @@ func (s *IssueLinkTypeService) UpdateWithContext(ctx context.Context, linkType * return &ret, resp, nil } -// Update wraps UpdateWithContext using the background context. -// Caller must close resp.Body -func (s *IssueLinkTypeService) Update(linkType *IssueLinkType) (*IssueLinkType, *Response, error) { - return s.UpdateWithContext(context.Background(), linkType) -} - -// DeleteWithContext deletes an issue link type based on provided ID. +// Delete deletes an issue link type based on provided ID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-delete // Caller must close resp.Body -func (s *IssueLinkTypeService) DeleteWithContext(ctx context.Context, ID string) (*Response, error) { +func (s *IssueLinkTypeService) Delete(ctx context.Context, ID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { @@ -131,9 +110,3 @@ func (s *IssueLinkTypeService) DeleteWithContext(ctx context.Context, ID string) resp, err := s.client.Do(req, nil) return resp, err } - -// Delete wraps DeleteWithContext using the background context. -// Caller must close resp.Body -func (s *IssueLinkTypeService) Delete(ID string) (*Response, error) { - return s.DeleteWithContext(context.Background(), ID) -} diff --git a/cloud/issuelinktype_test.go b/cloud/issuelinktype_test.go index f33a81b..23df4f4 100644 --- a/cloud/issuelinktype_test.go +++ b/cloud/issuelinktype_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestIssueLinkTypeService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - linkTypes, _, err := testClient.IssueLinkType.GetList() + linkTypes, _, err := testClient.IssueLinkType.GetList(context.Background()) if linkTypes == nil { t.Error("Expected issueLinkType list. LinkTypes is nil") } @@ -42,7 +43,7 @@ func TestIssueLinkTypeService_Get(t *testing.T) { "self": "https://www.example.com/jira/rest/api/2/issueLinkType/123"}`) }) - if linkType, _, err := testClient.IssueLinkType.Get("123"); err != nil { + if linkType, _, err := testClient.IssueLinkType.Get(context.Background(), "123"); err != nil { t.Errorf("Error given: %s", err) } else if linkType == nil { t.Error("Expected linkType. LinkType is nil") @@ -67,7 +68,7 @@ func TestIssueLinkTypeService_Create(t *testing.T) { Outward: "causes", } - if linkType, _, err := testClient.IssueLinkType.Create(lt); err != nil { + if linkType, _, err := testClient.IssueLinkType.Create(context.Background(), lt); err != nil { t.Errorf("Error given: %s", err) } else if linkType == nil { t.Error("Expected linkType. LinkType is nil") @@ -91,7 +92,7 @@ func TestIssueLinkTypeService_Update(t *testing.T) { Outward: "causes", } - if linkType, _, err := testClient.IssueLinkType.Update(lt); err != nil { + if linkType, _, err := testClient.IssueLinkType.Update(context.Background(), lt); err != nil { t.Errorf("Error given: %s", err) } else if linkType == nil { t.Error("Expected linkType. LinkType is nil") @@ -108,7 +109,7 @@ func TestIssueLinkTypeService_Delete(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - resp, err := testClient.IssueLinkType.Delete("100") + resp, err := testClient.IssueLinkType.Delete(context.Background(), "100") if resp.StatusCode != http.StatusNoContent { t.Error("Expected issue not deleted.") } diff --git a/onpremise/issuelinktype.go b/onpremise/issuelinktype.go index c6af34d..0589247 100644 --- a/onpremise/issuelinktype.go +++ b/onpremise/issuelinktype.go @@ -12,10 +12,10 @@ import ( // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Issue-link-types type IssueLinkTypeService service -// GetListWithContext gets all of the issue link types from Jira. +// GetList gets all of the issue link types from Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-get -func (s *IssueLinkTypeService) GetListWithContext(ctx context.Context) ([]IssueLinkType, *Response, error) { +func (s *IssueLinkTypeService) GetList(ctx context.Context) ([]IssueLinkType, *Response, error) { apiEndpoint := "rest/api/2/issueLinkType" req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -30,15 +30,10 @@ func (s *IssueLinkTypeService) GetListWithContext(ctx context.Context) ([]IssueL return linkTypeList, resp, nil } -// GetList wraps GetListWithContext using the background context. -func (s *IssueLinkTypeService) GetList() ([]IssueLinkType, *Response, error) { - return s.GetListWithContext(context.Background()) -} - -// GetWithContext gets info of a specific issue link type from Jira. +// Get gets info of a specific issue link type from Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-get -func (s *IssueLinkTypeService) GetWithContext(ctx context.Context, ID string) (*IssueLinkType, *Response, error) { +func (s *IssueLinkTypeService) Get(ctx context.Context, ID string) (*IssueLinkType, *Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) if err != nil { @@ -53,15 +48,10 @@ func (s *IssueLinkTypeService) GetWithContext(ctx context.Context, ID string) (* return linkType, resp, nil } -// Get wraps GetWithContext using the background context. -func (s *IssueLinkTypeService) Get(ID string) (*IssueLinkType, *Response, error) { - return s.GetWithContext(context.Background(), ID) -} - -// CreateWithContext creates an issue link type in Jira. +// Create creates an issue link type in Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-post -func (s *IssueLinkTypeService) CreateWithContext(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { +func (s *IssueLinkTypeService) Create(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := "/rest/api/2/issueLinkType" req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, linkType) if err != nil { @@ -88,16 +78,11 @@ func (s *IssueLinkTypeService) CreateWithContext(ctx context.Context, linkType * return linkType, resp, nil } -// Create wraps CreateWithContext using the background context. -func (s *IssueLinkTypeService) Create(linkType *IssueLinkType) (*IssueLinkType, *Response, error) { - return s.CreateWithContext(context.Background(), linkType) -} - -// UpdateWithContext updates an issue link type. The issue is found by key. +// Update updates an issue link type. The issue is found by key. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-put // Caller must close resp.Body -func (s *IssueLinkTypeService) UpdateWithContext(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { +func (s *IssueLinkTypeService) Update(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", linkType.ID) req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, linkType) if err != nil { @@ -111,17 +96,11 @@ func (s *IssueLinkTypeService) UpdateWithContext(ctx context.Context, linkType * return &ret, resp, nil } -// Update wraps UpdateWithContext using the background context. -// Caller must close resp.Body -func (s *IssueLinkTypeService) Update(linkType *IssueLinkType) (*IssueLinkType, *Response, error) { - return s.UpdateWithContext(context.Background(), linkType) -} - -// DeleteWithContext deletes an issue link type based on provided ID. +// Delete deletes an issue link type based on provided ID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-delete // Caller must close resp.Body -func (s *IssueLinkTypeService) DeleteWithContext(ctx context.Context, ID string) (*Response, error) { +func (s *IssueLinkTypeService) Delete(ctx context.Context, ID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { @@ -131,9 +110,3 @@ func (s *IssueLinkTypeService) DeleteWithContext(ctx context.Context, ID string) resp, err := s.client.Do(req, nil) return resp, err } - -// Delete wraps DeleteWithContext using the background context. -// Caller must close resp.Body -func (s *IssueLinkTypeService) Delete(ID string) (*Response, error) { - return s.DeleteWithContext(context.Background(), ID) -} diff --git a/onpremise/issuelinktype_test.go b/onpremise/issuelinktype_test.go index 397bb9a..9181632 100644 --- a/onpremise/issuelinktype_test.go +++ b/onpremise/issuelinktype_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestIssueLinkTypeService_GetList(t *testing.T) { fmt.Fprint(w, string(raw)) }) - linkTypes, _, err := testClient.IssueLinkType.GetList() + linkTypes, _, err := testClient.IssueLinkType.GetList(context.Background()) if linkTypes == nil { t.Error("Expected issueLinkType list. LinkTypes is nil") } @@ -42,7 +43,7 @@ func TestIssueLinkTypeService_Get(t *testing.T) { "self": "https://www.example.com/jira/rest/api/2/issueLinkType/123"}`) }) - if linkType, _, err := testClient.IssueLinkType.Get("123"); err != nil { + if linkType, _, err := testClient.IssueLinkType.Get(context.Background(), "123"); err != nil { t.Errorf("Error given: %s", err) } else if linkType == nil { t.Error("Expected linkType. LinkType is nil") @@ -67,7 +68,7 @@ func TestIssueLinkTypeService_Create(t *testing.T) { Outward: "causes", } - if linkType, _, err := testClient.IssueLinkType.Create(lt); err != nil { + if linkType, _, err := testClient.IssueLinkType.Create(context.Background(), lt); err != nil { t.Errorf("Error given: %s", err) } else if linkType == nil { t.Error("Expected linkType. LinkType is nil") @@ -91,7 +92,7 @@ func TestIssueLinkTypeService_Update(t *testing.T) { Outward: "causes", } - if linkType, _, err := testClient.IssueLinkType.Update(lt); err != nil { + if linkType, _, err := testClient.IssueLinkType.Update(context.Background(), lt); err != nil { t.Errorf("Error given: %s", err) } else if linkType == nil { t.Error("Expected linkType. LinkType is nil") @@ -108,7 +109,7 @@ func TestIssueLinkTypeService_Delete(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - resp, err := testClient.IssueLinkType.Delete("100") + resp, err := testClient.IssueLinkType.Delete(context.Background(), "100") if resp.StatusCode != http.StatusNoContent { t.Error("Expected issue not deleted.") } From 3b8985fe9e2a085a6055bd11744c69d3983cdf64 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 21:06:37 +0200 Subject: [PATCH 039/189] Board Service: Remove "WithContext" API methods --- cloud/board.go | 66 ++++++++++------------------------------- cloud/board_test.go | 19 ++++++------ onpremise/board.go | 66 ++++++++++------------------------------- onpremise/board_test.go | 19 ++++++------ 4 files changed, 50 insertions(+), 120 deletions(-) diff --git a/cloud/board.go b/cloud/board.go index 7ef0f15..c1e9504 100644 --- a/cloud/board.go +++ b/cloud/board.go @@ -125,10 +125,10 @@ type BoardConfigurationColumnStatus struct { Self string `json:"self"` } -// GetAllBoardsWithContext will returns all boards. This only includes boards that the user has permission to view. +// GetAllBoards will returns all boards. This only includes boards that the user has permission to view. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getAllBoards -func (s *BoardService) GetAllBoardsWithContext(ctx context.Context, opt *BoardListOptions) (*BoardsList, *Response, error) { +func (s *BoardService) GetAllBoards(ctx context.Context, opt *BoardListOptions) (*BoardsList, *Response, error) { apiEndpoint := "rest/agile/1.0/board" url, err := addOptions(apiEndpoint, opt) if err != nil { @@ -149,16 +149,11 @@ func (s *BoardService) GetAllBoardsWithContext(ctx context.Context, opt *BoardLi return boards, resp, err } -// GetAllBoards wraps GetAllBoardsWithContext using the background context. -func (s *BoardService) GetAllBoards(opt *BoardListOptions) (*BoardsList, *Response, error) { - return s.GetAllBoardsWithContext(context.Background(), opt) -} - -// GetBoardWithContext will returns the board for the given boardID. +// GetBoard will returns the board for the given boardID. // This board will only be returned if the user has permission to view it. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard -func (s *BoardService) GetBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) { +func (s *BoardService) GetBoard(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -175,12 +170,7 @@ func (s *BoardService) GetBoardWithContext(ctx context.Context, boardID int) (*B return board, resp, nil } -// GetBoard wraps GetBoardWithContext using the background context. -func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) { - return s.GetBoardWithContext(context.Background(), boardID) -} - -// CreateBoardWithContext creates a new board. Board name, type and filter Id is required. +// CreateBoard creates a new board. Board name, type and filter Id is required. // name - Must be less than 255 characters. // type - Valid values: scrum, kanban // filterId - Id of a filter that the user has permissions to view. @@ -188,7 +178,7 @@ func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) { // board will be created instead (remember that board sharing depends on the filter sharing). // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard -func (s *BoardService) CreateBoardWithContext(ctx context.Context, board *Board) (*Board, *Response, error) { +func (s *BoardService) CreateBoard(ctx context.Context, board *Board) (*Board, *Response, error) { apiEndpoint := "rest/agile/1.0/board" req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, board) if err != nil { @@ -205,16 +195,11 @@ func (s *BoardService) CreateBoardWithContext(ctx context.Context, board *Board) return responseBoard, resp, nil } -// CreateBoard wraps CreateBoardWithContext using the background context. -func (s *BoardService) CreateBoard(board *Board) (*Board, *Response, error) { - return s.CreateBoardWithContext(context.Background(), board) -} - -// DeleteBoardWithContext will delete an agile board. +// DeleteBoard will delete an agile board. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-deleteBoard // Caller must close resp.Body -func (s *BoardService) DeleteBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) { +func (s *BoardService) DeleteBoard(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { @@ -228,23 +213,17 @@ func (s *BoardService) DeleteBoardWithContext(ctx context.Context, boardID int) return nil, resp, err } -// DeleteBoard wraps DeleteBoardWithContext using the background context. -// Caller must close resp.Body -func (s *BoardService) DeleteBoard(boardID int) (*Board, *Response, error) { - return s.DeleteBoardWithContext(context.Background(), boardID) -} - -// GetAllSprintsWithContext will return all sprints from a board, for a given board Id. +// GetAllSprints will return all sprints from a board, for a given board Id. // This only includes sprints that the user has permission to view. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint -func (s *BoardService) GetAllSprintsWithContext(ctx context.Context, boardID string) ([]Sprint, *Response, error) { +func (s *BoardService) GetAllSprints(ctx context.Context, boardID string) ([]Sprint, *Response, error) { id, err := strconv.Atoi(boardID) if err != nil { return nil, nil, err } - result, response, err := s.GetAllSprintsWithOptions(id, &GetAllSprintsOptions{}) + result, response, err := s.GetAllSprintsWithOptions(ctx, id, &GetAllSprintsOptions{}) if err != nil { return nil, nil, err } @@ -252,16 +231,11 @@ func (s *BoardService) GetAllSprintsWithContext(ctx context.Context, boardID str return result.Values, response, nil } -// GetAllSprints wraps GetAllSprintsWithContext using the background context. -func (s *BoardService) GetAllSprints(boardID string) ([]Sprint, *Response, error) { - return s.GetAllSprintsWithContext(context.Background(), boardID) -} - -// GetAllSprintsWithOptionsWithContext will return sprints from a board, for a given board Id and filtering options +// GetAllSprintsWithOptions will return sprints from a board, for a given board Id and filtering options // This only includes sprints that the user has permission to view. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint -func (s *BoardService) GetAllSprintsWithOptionsWithContext(ctx context.Context, boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { +func (s *BoardService) GetAllSprintsWithOptions(ctx context.Context, boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/sprint", boardID) url, err := addOptions(apiEndpoint, options) if err != nil { @@ -281,14 +255,9 @@ func (s *BoardService) GetAllSprintsWithOptionsWithContext(ctx context.Context, return result, resp, err } -// GetAllSprintsWithOptions wraps GetAllSprintsWithOptionsWithContext using the background context. -func (s *BoardService) GetAllSprintsWithOptions(boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { - return s.GetAllSprintsWithOptionsWithContext(context.Background(), boardID, options) -} - -// GetBoardConfigurationWithContext will return a board configuration for a given board Id +// GetBoardConfiguration will return a board configuration for a given board Id // Jira API docs:https://developer.atlassian.com/cloud/jira/software/rest/#api-rest-agile-1-0-board-boardId-configuration-get -func (s *BoardService) GetBoardConfigurationWithContext(ctx context.Context, boardID int) (*BoardConfiguration, *Response, error) { +func (s *BoardService) GetBoardConfiguration(ctx context.Context, boardID int) (*BoardConfiguration, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/configuration", boardID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) @@ -306,8 +275,3 @@ func (s *BoardService) GetBoardConfigurationWithContext(ctx context.Context, boa return result, resp, err } - -// GetBoardConfiguration wraps GetBoardConfigurationWithContext using the background context. -func (s *BoardService) GetBoardConfiguration(boardID int) (*BoardConfiguration, *Response, error) { - return s.GetBoardConfigurationWithContext(context.Background(), boardID) -} diff --git a/cloud/board_test.go b/cloud/board_test.go index 699776c..b2861f2 100644 --- a/cloud/board_test.go +++ b/cloud/board_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestBoardService_GetAllBoards(t *testing.T) { fmt.Fprint(w, string(raw)) }) - projects, _, err := testClient.Board.GetAllBoards(nil) + projects, _, err := testClient.Board.GetAllBoards(context.Background(), nil) if projects == nil { t.Error("Expected boards list. Boards list is nil") } @@ -56,7 +57,7 @@ func TestBoardService_GetAllBoards_WithFilter(t *testing.T) { boardsListOptions.StartAt = 1 boardsListOptions.MaxResults = 10 - projects, _, err := testClient.Board.GetAllBoards(boardsListOptions) + projects, _, err := testClient.Board.GetAllBoards(context.Background(), boardsListOptions) if projects == nil { t.Error("Expected boards list. Boards list is nil") } @@ -76,7 +77,7 @@ func TestBoardService_GetBoard(t *testing.T) { fmt.Fprint(w, `{"id":4,"self":"https://test.jira.org/rest/agile/1.0/board/1","name":"Test Weekly","type":"scrum"}`) }) - board, _, err := testClient.Board.GetBoard(1) + board, _, err := testClient.Board.GetBoard(context.Background(), 1) if board == nil { t.Error("Expected board list. Board list is nil") } @@ -96,7 +97,7 @@ func TestBoardService_GetBoard_WrongID(t *testing.T) { fmt.Fprint(w, nil) }) - board, resp, err := testClient.Board.GetBoard(99999999) + board, resp, err := testClient.Board.GetBoard(context.Background(), 99999999) if board != nil { t.Errorf("Expected nil. Got %s", err) } @@ -125,7 +126,7 @@ func TestBoardService_CreateBoard(t *testing.T) { Type: "kanban", FilterID: 17, } - issue, _, err := testClient.Board.CreateBoard(b) + issue, _, err := testClient.Board.CreateBoard(context.Background(), b) if issue == nil { t.Error("Expected board. Board is nil") } @@ -145,7 +146,7 @@ func TestBoardService_DeleteBoard(t *testing.T) { fmt.Fprint(w, `{}`) }) - _, resp, err := testClient.Board.DeleteBoard(1) + _, resp, err := testClient.Board.DeleteBoard(context.Background(), 1) if resp.StatusCode != 204 { t.Error("Expected board not deleted.") } @@ -171,7 +172,7 @@ func TestBoardService_GetAllSprints(t *testing.T) { fmt.Fprint(w, string(raw)) }) - sprints, _, err := testClient.Board.GetAllSprints("123") + sprints, _, err := testClient.Board.GetAllSprints(context.Background(), "123") if err != nil { t.Errorf("Got error: %v", err) @@ -203,7 +204,7 @@ func TestBoardService_GetAllSprintsWithOptions(t *testing.T) { fmt.Fprint(w, string(raw)) }) - sprints, _, err := testClient.Board.GetAllSprintsWithOptions(123, &GetAllSprintsOptions{State: "active,future"}) + sprints, _, err := testClient.Board.GetAllSprintsWithOptions(context.Background(), 123, &GetAllSprintsOptions{State: "active,future"}) if err != nil { t.Errorf("Got error: %v", err) } @@ -234,7 +235,7 @@ func TestBoardService_GetBoardConfigoration(t *testing.T) { fmt.Fprint(w, string(raw)) }) - boardConfiguration, _, err := testClient.Board.GetBoardConfiguration(35) + boardConfiguration, _, err := testClient.Board.GetBoardConfiguration(context.Background(), 35) if err != nil { t.Errorf("Got error: %v", err) } diff --git a/onpremise/board.go b/onpremise/board.go index d85e0a0..019f4af 100644 --- a/onpremise/board.go +++ b/onpremise/board.go @@ -125,10 +125,10 @@ type BoardConfigurationColumnStatus struct { Self string `json:"self"` } -// GetAllBoardsWithContext will returns all boards. This only includes boards that the user has permission to view. +// GetAllBoards will returns all boards. This only includes boards that the user has permission to view. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getAllBoards -func (s *BoardService) GetAllBoardsWithContext(ctx context.Context, opt *BoardListOptions) (*BoardsList, *Response, error) { +func (s *BoardService) GetAllBoards(ctx context.Context, opt *BoardListOptions) (*BoardsList, *Response, error) { apiEndpoint := "rest/agile/1.0/board" url, err := addOptions(apiEndpoint, opt) if err != nil { @@ -149,16 +149,11 @@ func (s *BoardService) GetAllBoardsWithContext(ctx context.Context, opt *BoardLi return boards, resp, err } -// GetAllBoards wraps GetAllBoardsWithContext using the background context. -func (s *BoardService) GetAllBoards(opt *BoardListOptions) (*BoardsList, *Response, error) { - return s.GetAllBoardsWithContext(context.Background(), opt) -} - -// GetBoardWithContext will returns the board for the given boardID. +// GetBoard will returns the board for the given boardID. // This board will only be returned if the user has permission to view it. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard -func (s *BoardService) GetBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) { +func (s *BoardService) GetBoard(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -175,12 +170,7 @@ func (s *BoardService) GetBoardWithContext(ctx context.Context, boardID int) (*B return board, resp, nil } -// GetBoard wraps GetBoardWithContext using the background context. -func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) { - return s.GetBoardWithContext(context.Background(), boardID) -} - -// CreateBoardWithContext creates a new board. Board name, type and filter Id is required. +// CreateBoard creates a new board. Board name, type and filter Id is required. // name - Must be less than 255 characters. // type - Valid values: scrum, kanban // filterId - Id of a filter that the user has permissions to view. @@ -188,7 +178,7 @@ func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) { // board will be created instead (remember that board sharing depends on the filter sharing). // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard -func (s *BoardService) CreateBoardWithContext(ctx context.Context, board *Board) (*Board, *Response, error) { +func (s *BoardService) CreateBoard(ctx context.Context, board *Board) (*Board, *Response, error) { apiEndpoint := "rest/agile/1.0/board" req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, board) if err != nil { @@ -205,16 +195,11 @@ func (s *BoardService) CreateBoardWithContext(ctx context.Context, board *Board) return responseBoard, resp, nil } -// CreateBoard wraps CreateBoardWithContext using the background context. -func (s *BoardService) CreateBoard(board *Board) (*Board, *Response, error) { - return s.CreateBoardWithContext(context.Background(), board) -} - -// DeleteBoardWithContext will delete an agile board. +// DeleteBoard will delete an agile board. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-deleteBoard // Caller must close resp.Body -func (s *BoardService) DeleteBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) { +func (s *BoardService) DeleteBoard(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { @@ -228,23 +213,17 @@ func (s *BoardService) DeleteBoardWithContext(ctx context.Context, boardID int) return nil, resp, err } -// DeleteBoard wraps DeleteBoardWithContext using the background context. -// Caller must close resp.Body -func (s *BoardService) DeleteBoard(boardID int) (*Board, *Response, error) { - return s.DeleteBoardWithContext(context.Background(), boardID) -} - -// GetAllSprintsWithContext will return all sprints from a board, for a given board Id. +// GetAllSprints will return all sprints from a board, for a given board Id. // This only includes sprints that the user has permission to view. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint -func (s *BoardService) GetAllSprintsWithContext(ctx context.Context, boardID string) ([]Sprint, *Response, error) { +func (s *BoardService) GetAllSprints(ctx context.Context, boardID string) ([]Sprint, *Response, error) { id, err := strconv.Atoi(boardID) if err != nil { return nil, nil, err } - result, response, err := s.GetAllSprintsWithOptions(id, &GetAllSprintsOptions{}) + result, response, err := s.GetAllSprintsWithOptions(ctx, id, &GetAllSprintsOptions{}) if err != nil { return nil, nil, err } @@ -252,16 +231,11 @@ func (s *BoardService) GetAllSprintsWithContext(ctx context.Context, boardID str return result.Values, response, nil } -// GetAllSprints wraps GetAllSprintsWithContext using the background context. -func (s *BoardService) GetAllSprints(boardID string) ([]Sprint, *Response, error) { - return s.GetAllSprintsWithContext(context.Background(), boardID) -} - -// GetAllSprintsWithOptionsWithContext will return sprints from a board, for a given board Id and filtering options +// GetAllSprintsWithOptions will return sprints from a board, for a given board Id and filtering options // This only includes sprints that the user has permission to view. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint -func (s *BoardService) GetAllSprintsWithOptionsWithContext(ctx context.Context, boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { +func (s *BoardService) GetAllSprintsWithOptions(ctx context.Context, boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/sprint", boardID) url, err := addOptions(apiEndpoint, options) if err != nil { @@ -281,14 +255,9 @@ func (s *BoardService) GetAllSprintsWithOptionsWithContext(ctx context.Context, return result, resp, err } -// GetAllSprintsWithOptions wraps GetAllSprintsWithOptionsWithContext using the background context. -func (s *BoardService) GetAllSprintsWithOptions(boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { - return s.GetAllSprintsWithOptionsWithContext(context.Background(), boardID, options) -} - -// GetBoardConfigurationWithContext will return a board configuration for a given board Id +// GetBoardConfiguration will return a board configuration for a given board Id // Jira API docs:https://developer.atlassian.com/cloud/jira/software/rest/#api-rest-agile-1-0-board-boardId-configuration-get -func (s *BoardService) GetBoardConfigurationWithContext(ctx context.Context, boardID int) (*BoardConfiguration, *Response, error) { +func (s *BoardService) GetBoardConfiguration(ctx context.Context, boardID int) (*BoardConfiguration, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/configuration", boardID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) @@ -306,8 +275,3 @@ func (s *BoardService) GetBoardConfigurationWithContext(ctx context.Context, boa return result, resp, err } - -// GetBoardConfiguration wraps GetBoardConfigurationWithContext using the background context. -func (s *BoardService) GetBoardConfiguration(boardID int) (*BoardConfiguration, *Response, error) { - return s.GetBoardConfigurationWithContext(context.Background(), boardID) -} diff --git a/onpremise/board_test.go b/onpremise/board_test.go index ccdcae8..edfcb19 100644 --- a/onpremise/board_test.go +++ b/onpremise/board_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "fmt" "net/http" "os" @@ -22,7 +23,7 @@ func TestBoardService_GetAllBoards(t *testing.T) { fmt.Fprint(w, string(raw)) }) - projects, _, err := testClient.Board.GetAllBoards(nil) + projects, _, err := testClient.Board.GetAllBoards(context.Background(), nil) if projects == nil { t.Error("Expected boards list. Boards list is nil") } @@ -56,7 +57,7 @@ func TestBoardService_GetAllBoards_WithFilter(t *testing.T) { boardsListOptions.StartAt = 1 boardsListOptions.MaxResults = 10 - projects, _, err := testClient.Board.GetAllBoards(boardsListOptions) + projects, _, err := testClient.Board.GetAllBoards(context.Background(), boardsListOptions) if projects == nil { t.Error("Expected boards list. Boards list is nil") } @@ -76,7 +77,7 @@ func TestBoardService_GetBoard(t *testing.T) { fmt.Fprint(w, `{"id":4,"self":"https://test.jira.org/rest/agile/1.0/board/1","name":"Test Weekly","type":"scrum"}`) }) - board, _, err := testClient.Board.GetBoard(1) + board, _, err := testClient.Board.GetBoard(context.Background(), 1) if board == nil { t.Error("Expected board list. Board list is nil") } @@ -96,7 +97,7 @@ func TestBoardService_GetBoard_WrongID(t *testing.T) { fmt.Fprint(w, nil) }) - board, resp, err := testClient.Board.GetBoard(99999999) + board, resp, err := testClient.Board.GetBoard(context.Background(), 99999999) if board != nil { t.Errorf("Expected nil. Got %s", err) } @@ -125,7 +126,7 @@ func TestBoardService_CreateBoard(t *testing.T) { Type: "kanban", FilterID: 17, } - issue, _, err := testClient.Board.CreateBoard(b) + issue, _, err := testClient.Board.CreateBoard(context.Background(), b) if issue == nil { t.Error("Expected board. Board is nil") } @@ -145,7 +146,7 @@ func TestBoardService_DeleteBoard(t *testing.T) { fmt.Fprint(w, `{}`) }) - _, resp, err := testClient.Board.DeleteBoard(1) + _, resp, err := testClient.Board.DeleteBoard(context.Background(), 1) if resp.StatusCode != 204 { t.Error("Expected board not deleted.") } @@ -171,7 +172,7 @@ func TestBoardService_GetAllSprints(t *testing.T) { fmt.Fprint(w, string(raw)) }) - sprints, _, err := testClient.Board.GetAllSprints("123") + sprints, _, err := testClient.Board.GetAllSprints(context.Background(), "123") if err != nil { t.Errorf("Got error: %v", err) @@ -203,7 +204,7 @@ func TestBoardService_GetAllSprintsWithOptions(t *testing.T) { fmt.Fprint(w, string(raw)) }) - sprints, _, err := testClient.Board.GetAllSprintsWithOptions(123, &GetAllSprintsOptions{State: "active,future"}) + sprints, _, err := testClient.Board.GetAllSprintsWithOptions(context.Background(), 123, &GetAllSprintsOptions{State: "active,future"}) if err != nil { t.Errorf("Got error: %v", err) } @@ -234,7 +235,7 @@ func TestBoardService_GetBoardConfigoration(t *testing.T) { fmt.Fprint(w, string(raw)) }) - boardConfiguration, _, err := testClient.Board.GetBoardConfiguration(35) + boardConfiguration, _, err := testClient.Board.GetBoardConfiguration(context.Background(), 35) if err != nil { t.Errorf("Got error: %v", err) } From fb4caf4642732769dfc2065d68a9eccd351a5442 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 21:06:54 +0200 Subject: [PATCH 040/189] Issue Service: Remove "WithContext" API methods --- cloud/auth_transport_jwt_test.go | 3 +- cloud/examples/addlabel/main.go | 5 +- cloud/examples/create/main.go | 3 +- cloud/examples/createwithcustomfields/main.go | 3 +- cloud/examples/ignorecerts/main.go | 3 +- cloud/examples/jql/main.go | 5 +- cloud/examples/newclient/main.go | 3 +- cloud/examples/pagination/main.go | 3 +- cloud/examples/renderedfields/main.go | 3 +- cloud/examples/searchpages/main.go | 3 +- cloud/issue.go | 290 ++++-------------- cloud/issue_test.go | 91 +++--- onpremise/auth_transport_jwt_test.go | 3 +- onpremise/examples/addlabel/main.go | 5 +- onpremise/examples/create/main.go | 3 +- .../examples/createwithcustomfields/main.go | 3 +- onpremise/examples/ignorecerts/main.go | 3 +- onpremise/examples/jql/main.go | 5 +- onpremise/examples/newclient/main.go | 3 +- onpremise/examples/pagination/main.go | 3 +- onpremise/examples/renderedfields/main.go | 3 +- onpremise/examples/searchpages/main.go | 3 +- onpremise/issue.go | 290 ++++-------------- onpremise/issue_test.go | 91 +++--- 24 files changed, 264 insertions(+), 566 deletions(-) diff --git a/cloud/auth_transport_jwt_test.go b/cloud/auth_transport_jwt_test.go index 2ce50f2..6a68070 100644 --- a/cloud/auth_transport_jwt_test.go +++ b/cloud/auth_transport_jwt_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "net/http" "strings" "testing" @@ -27,5 +28,5 @@ func TestJWTAuthTransport_HeaderContainsJWT(t *testing.T) { }) jwtClient, _ := NewClient(testServer.URL, jwtTransport.Client()) - jwtClient.Issue.Get("TEST-1", nil) + jwtClient.Issue.Get(context.Background(), "TEST-1", nil) } diff --git a/cloud/examples/addlabel/main.go b/cloud/examples/addlabel/main.go index ca5a6f7..12adcb1 100644 --- a/cloud/examples/addlabel/main.go +++ b/cloud/examples/addlabel/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "context" "fmt" "io" "os" @@ -62,7 +63,7 @@ func main() { }, } - resp, err := client.Issue.UpdateIssue(issueId, c) + resp, err := client.Issue.UpdateIssue(context.Background(), issueId, c) if err != nil { fmt.Println(err) @@ -70,7 +71,7 @@ func main() { body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) - issue, _, _ := client.Issue.Get(issueId, nil) + issue, _, _ := client.Issue.Get(context.Background(), issueId, nil) fmt.Printf("Issue: %s:%s\n", issue.Key, issue.Fields.Summary) fmt.Printf("\tLabels: %+v\n", issue.Fields.Labels) diff --git a/cloud/examples/create/main.go b/cloud/examples/create/main.go index 2dc3837..2734e6c 100644 --- a/cloud/examples/create/main.go +++ b/cloud/examples/create/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "context" "fmt" "os" "strings" @@ -54,7 +55,7 @@ func main() { }, } - issue, _, err := client.Issue.Create(&i) + issue, _, err := client.Issue.Create(context.Background(), &i) if err != nil { panic(err) } diff --git a/cloud/examples/createwithcustomfields/main.go b/cloud/examples/createwithcustomfields/main.go index 68b32b5..0f597f2 100644 --- a/cloud/examples/createwithcustomfields/main.go +++ b/cloud/examples/createwithcustomfields/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "context" "fmt" "os" "strings" @@ -65,7 +66,7 @@ func main() { }, } - issue, _, err := client.Issue.Create(&i) + issue, _, err := client.Issue.Create(context.Background(), &i) if err != nil { panic(err) } diff --git a/cloud/examples/ignorecerts/main.go b/cloud/examples/ignorecerts/main.go index 525cd51..078a489 100644 --- a/cloud/examples/ignorecerts/main.go +++ b/cloud/examples/ignorecerts/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "crypto/tls" "fmt" "net/http" @@ -15,7 +16,7 @@ func main() { client := &http.Client{Transport: tr} jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", client) - issue, _, _ := jiraClient.Issue.Get("MESOS-3325", nil) + issue, _, _ := jiraClient.Issue.Get(context.Background(), "MESOS-3325", nil) fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary) fmt.Printf("Type: %s\n", issue.Fields.Type.Name) diff --git a/cloud/examples/jql/main.go b/cloud/examples/jql/main.go index ce3ba45..b5161b9 100644 --- a/cloud/examples/jql/main.go +++ b/cloud/examples/jql/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" jira "github.com/andygrunwald/go-jira/cloud" @@ -13,7 +14,7 @@ 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, err := jiraClient.Issue.Search(context.Background(), jql, nil) if err != nil { panic(err) } @@ -25,7 +26,7 @@ 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, err = jiraClient.Issue.Search(context.Background(), jql, nil) if err != nil { panic(err) } diff --git a/cloud/examples/newclient/main.go b/cloud/examples/newclient/main.go index c03461d..dea1ecf 100644 --- a/cloud/examples/newclient/main.go +++ b/cloud/examples/newclient/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" jira "github.com/andygrunwald/go-jira/cloud" @@ -8,7 +9,7 @@ import ( func main() { jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", nil) - issue, _, _ := jiraClient.Issue.Get("MESOS-3325", nil) + issue, _, _ := jiraClient.Issue.Get(context.Background(), "MESOS-3325", nil) fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary) fmt.Printf("Type: %s\n", issue.Fields.Type.Name) diff --git a/cloud/examples/pagination/main.go b/cloud/examples/pagination/main.go index 07c52ee..9a2d2bd 100644 --- a/cloud/examples/pagination/main.go +++ b/cloud/examples/pagination/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" jira "github.com/andygrunwald/go-jira/cloud" @@ -19,7 +20,7 @@ func GetAllIssues(client *jira.Client, searchString string) ([]jira.Issue, error StartAt: last, } - chunk, resp, err := client.Issue.Search(searchString, opt) + chunk, resp, err := client.Issue.Search(context.Background(), searchString, opt) if err != nil { return nil, err } diff --git a/cloud/examples/renderedfields/main.go b/cloud/examples/renderedfields/main.go index 274498f..8671120 100644 --- a/cloud/examples/renderedfields/main.go +++ b/cloud/examples/renderedfields/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "context" "fmt" "net/http" "os" @@ -52,7 +53,7 @@ func main() { fmt.Printf("Targeting %s for issue %s\n", strings.TrimSpace(jiraURL), key) options := &jira.GetQueryOptions{Expand: "renderedFields"} - u, _, err := client.Issue.Get(key, options) + u, _, err := client.Issue.Get(context.Background(), key, options) if err != nil { fmt.Printf("\n==> error: %v\n", err) diff --git a/cloud/examples/searchpages/main.go b/cloud/examples/searchpages/main.go index 51db937..c98f585 100644 --- a/cloud/examples/searchpages/main.go +++ b/cloud/examples/searchpages/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "context" "fmt" "log" "os" @@ -49,7 +50,7 @@ func main() { // SearchPages will page through results and pass each issue to appendFunc // In this example, we'll search for all the issues in the target project - err = client.Issue.SearchPages(fmt.Sprintf(`project=%s`, strings.TrimSpace(jiraPK)), nil, appendFunc) + err = client.Issue.SearchPages(context.Background(), fmt.Sprintf(`project=%s`, strings.TrimSpace(jiraPK)), nil, appendFunc) if err != nil { log.Fatal(err) } diff --git a/cloud/issue.go b/cloud/issue.go index 8570060..a0b89c1 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -605,7 +605,7 @@ type RemoteLinkStatus struct { Icon *RemoteLinkIcon `json:"icon,omitempty" structs:"icon,omitempty"` } -// GetWithContext returns a full representation of the issue for the given issue key. +// Get returns a full representation of the issue for the given issue key. // Jira will attempt to identify the issue by the issueIdOrKey path parameter. // This can be an issue id, or an issue key. // If the issue cannot be found via an exact match, Jira will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved. @@ -613,7 +613,7 @@ type RemoteLinkStatus struct { // # The given options will be appended to the query string // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getIssue -func (s *IssueService) GetWithContext(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { +func (s *IssueService) Get(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -638,16 +638,11 @@ func (s *IssueService) GetWithContext(ctx context.Context, issueID string, optio return issue, resp, nil } -// Get wraps GetWithContext using the background context. -func (s *IssueService) Get(issueID string, options *GetQueryOptions) (*Issue, *Response, error) { - return s.GetWithContext(context.Background(), issueID, options) -} - -// DownloadAttachmentWithContext returns a Response of an attachment for a given attachmentID. +// DownloadAttachment returns a Response of an attachment for a given attachmentID. // The attachment is in the Response.Body of the response. // This is an io.ReadCloser. // Caller must close resp.Body. -func (s *IssueService) DownloadAttachmentWithContext(ctx context.Context, attachmentID string) (*Response, error) { +func (s *IssueService) DownloadAttachment(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("secure/attachment/%s/", attachmentID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -663,14 +658,8 @@ func (s *IssueService) DownloadAttachmentWithContext(ctx context.Context, attach return resp, nil } -// DownloadAttachment wraps DownloadAttachmentWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) DownloadAttachment(attachmentID string) (*Response, error) { - return s.DownloadAttachmentWithContext(context.Background(), attachmentID) -} - -// PostAttachmentWithContext uploads r (io.Reader) as an attachment to a given issueID -func (s *IssueService) PostAttachmentWithContext(ctx context.Context, issueID string, r io.Reader, attachmentName string) (*[]Attachment, *Response, error) { +// PostAttachment uploads r (io.Reader) as an attachment to a given issueID +func (s *IssueService) PostAttachment(ctx context.Context, issueID string, r io.Reader, attachmentName string) (*[]Attachment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/attachments", issueID) b := new(bytes.Buffer) @@ -707,14 +696,9 @@ func (s *IssueService) PostAttachmentWithContext(ctx context.Context, issueID st return attachment, resp, nil } -// PostAttachment wraps PostAttachmentWithContext using the background context. -func (s *IssueService) PostAttachment(issueID string, r io.Reader, attachmentName string) (*[]Attachment, *Response, error) { - return s.PostAttachmentWithContext(context.Background(), issueID, r, attachmentName) -} - -// DeleteAttachmentWithContext deletes an attachment of a given attachmentID +// DeleteAttachment deletes an attachment of a given attachmentID // Caller must close resp.Body -func (s *IssueService) DeleteAttachmentWithContext(ctx context.Context, attachmentID string) (*Response, error) { +func (s *IssueService) DeleteAttachment(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/attachment/%s", attachmentID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) @@ -731,15 +715,9 @@ func (s *IssueService) DeleteAttachmentWithContext(ctx context.Context, attachme return resp, nil } -// DeleteAttachment wraps DeleteAttachmentWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) DeleteAttachment(attachmentID string) (*Response, error) { - return s.DeleteAttachmentWithContext(context.Background(), attachmentID) -} - -// DeleteLinkWithContext deletes a link of a given linkID +// DeleteLink deletes a link of a given linkID // Caller must close resp.Body -func (s *IssueService) DeleteLinkWithContext(ctx context.Context, linkID string) (*Response, error) { +func (s *IssueService) DeleteLink(ctx context.Context, linkID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLink/%s", linkID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) @@ -756,17 +734,11 @@ func (s *IssueService) DeleteLinkWithContext(ctx context.Context, linkID string) return resp, nil } -// DeleteLink wraps DeleteLinkWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) DeleteLink(linkID string) (*Response, error) { - return s.DeleteLinkWithContext(context.Background(), linkID) -} - -// GetWorklogsWithContext gets all the worklogs for an issue. +// GetWorklogs gets all the worklogs for an issue. // This method is especially important if you need to read all the worklogs, not just the first page. // // https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/worklog-getIssueWorklog -func (s *IssueService) GetWorklogsWithContext(ctx context.Context, issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) { +func (s *IssueService) GetWorklogs(ctx context.Context, issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) @@ -786,11 +758,6 @@ func (s *IssueService) GetWorklogsWithContext(ctx context.Context, issueID strin return v, resp, err } -// GetWorklogs wraps GetWorklogsWithContext using the background context. -func (s *IssueService) GetWorklogs(issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) { - return s.GetWorklogsWithContext(context.Background(), issueID, options...) -} - // Applies query options to http request. // This helper is meant to be used with all "QueryOptions" structs. func WithQueryOptions(options interface{}) func(*http.Request) error { @@ -807,12 +774,12 @@ func WithQueryOptions(options interface{}) func(*http.Request) error { } } -// CreateWithContext creates an issue or a sub-task from a JSON representation. +// Create creates an issue or a sub-task from a JSON representation. // Creating a sub-task is similar to creating a regular issue, with two important differences: // The issueType field must correspond to a sub-task issue type and you must provide a parent field in the issue create request containing the id or key of the parent issue. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-createIssues -func (s *IssueService) CreateWithContext(ctx context.Context, issue *Issue) (*Issue, *Response, error) { +func (s *IssueService) Create(ctx context.Context, issue *Issue) (*Issue, *Response, error) { apiEndpoint := "rest/api/2/issue" req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, issue) if err != nil { @@ -837,17 +804,12 @@ func (s *IssueService) CreateWithContext(ctx context.Context, issue *Issue) (*Is return responseIssue, resp, nil } -// Create wraps CreateWithContext using the background context. -func (s *IssueService) Create(issue *Issue) (*Issue, *Response, error) { - return s.CreateWithContext(context.Background(), issue) -} - -// UpdateWithOptionsWithContext updates an issue from a JSON representation, +// UpdateWithOptions updates an issue from a JSON representation, // while also specifying query params. The issue is found by key. // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-editIssue // Caller must close resp.Body -func (s *IssueService) UpdateWithOptionsWithContext(ctx context.Context, issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) { +func (s *IssueService) UpdateWithOptions(ctx context.Context, issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", issue.Key) url, err := addOptions(apiEndpoint, opts) if err != nil { @@ -869,29 +831,18 @@ func (s *IssueService) UpdateWithOptionsWithContext(ctx context.Context, issue * return &ret, resp, nil } -// UpdateWithOptions wraps UpdateWithOptionsWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) UpdateWithOptions(issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) { - return s.UpdateWithOptionsWithContext(context.Background(), issue, opts) -} - -// UpdateWithContext updates an issue from a JSON representation. The issue is found by key. +// Update updates an issue from a JSON representation. The issue is found by key. // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-editIssue -func (s *IssueService) UpdateWithContext(ctx context.Context, issue *Issue) (*Issue, *Response, error) { - return s.UpdateWithOptionsWithContext(ctx, issue, nil) +func (s *IssueService) Update(ctx context.Context, issue *Issue) (*Issue, *Response, error) { + return s.UpdateWithOptions(ctx, issue, nil) } -// Update wraps UpdateWithContext using the background context. -func (s *IssueService) Update(issue *Issue) (*Issue, *Response, error) { - return s.UpdateWithContext(context.Background(), issue) -} - -// UpdateIssueWithContext updates an issue from a JSON representation. The issue is found by key. +// UpdateIssue updates an issue from a JSON representation. The issue is found by key. // // https://docs.atlassian.com/jira/REST/7.4.0/#api/2/issue-editIssue // Caller must close resp.Body -func (s *IssueService) UpdateIssueWithContext(ctx context.Context, jiraID string, data map[string]interface{}) (*Response, error) { +func (s *IssueService) UpdateIssue(ctx context.Context, jiraID string, data map[string]interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", jiraID) req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, data) if err != nil { @@ -907,16 +858,10 @@ func (s *IssueService) UpdateIssueWithContext(ctx context.Context, jiraID string return resp, nil } -// UpdateIssue wraps UpdateIssueWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) UpdateIssue(jiraID string, data map[string]interface{}) (*Response, error) { - return s.UpdateIssueWithContext(context.Background(), jiraID, data) -} - -// AddCommentWithContext adds a new comment to issueID. +// AddComment adds a new comment to issueID. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-addComment -func (s *IssueService) AddCommentWithContext(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { +func (s *IssueService) AddComment(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment", issueID) req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, comment) if err != nil { @@ -933,15 +878,10 @@ func (s *IssueService) AddCommentWithContext(ctx context.Context, issueID string return responseComment, resp, nil } -// AddComment wraps AddCommentWithContext using the background context. -func (s *IssueService) AddComment(issueID string, comment *Comment) (*Comment, *Response, error) { - return s.AddCommentWithContext(context.Background(), issueID, comment) -} - -// UpdateCommentWithContext updates the body of a comment, identified by comment.ID, on the issueID. +// UpdateComment updates the body of a comment, identified by comment.ID, on the issueID. // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/comment-updateComment -func (s *IssueService) UpdateCommentWithContext(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { +func (s *IssueService) UpdateComment(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { reqBody := struct { Body string `json:"body"` }{ @@ -962,15 +902,10 @@ func (s *IssueService) UpdateCommentWithContext(ctx context.Context, issueID str return responseComment, resp, nil } -// UpdateComment wraps UpdateCommentWithContext using the background context. -func (s *IssueService) UpdateComment(issueID string, comment *Comment) (*Comment, *Response, error) { - return s.UpdateCommentWithContext(context.Background(), issueID, comment) -} - -// DeleteCommentWithContext Deletes a comment from an issueID. +// DeleteComment Deletes a comment from an issueID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-issue-issueIdOrKey-comment-id-delete -func (s *IssueService) DeleteCommentWithContext(ctx context.Context, issueID, commentID string) error { +func (s *IssueService) DeleteComment(ctx context.Context, issueID, commentID string) error { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, commentID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { @@ -987,15 +922,10 @@ func (s *IssueService) DeleteCommentWithContext(ctx context.Context, issueID, co return nil } -// DeleteComment wraps DeleteCommentWithContext using the background context. -func (s *IssueService) DeleteComment(issueID, commentID string) error { - return s.DeleteCommentWithContext(context.Background(), issueID, commentID) -} - -// AddWorklogRecordWithContext adds a new worklog record to issueID. +// AddWorklogRecord adds a new worklog record to issueID. // // https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-worklog-post -func (s *IssueService) AddWorklogRecordWithContext(ctx context.Context, issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { +func (s *IssueService) AddWorklogRecord(ctx context.Context, issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, record) if err != nil { @@ -1019,15 +949,10 @@ func (s *IssueService) AddWorklogRecordWithContext(ctx context.Context, issueID return responseRecord, resp, nil } -// AddWorklogRecord wraps AddWorklogRecordWithContext using the background context. -func (s *IssueService) AddWorklogRecord(issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { - return s.AddWorklogRecordWithContext(context.Background(), issueID, record, options...) -} - -// UpdateWorklogRecordWithContext updates a worklog record. +// UpdateWorklogRecord updates a worklog record. // // https://docs.atlassian.com/software/jira/docs/api/REST/7.1.2/#api/2/issue-updateWorklog -func (s *IssueService) UpdateWorklogRecordWithContext(ctx context.Context, issueID, worklogID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { +func (s *IssueService) UpdateWorklogRecord(ctx context.Context, issueID, worklogID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog/%s", issueID, worklogID) req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, record) if err != nil { @@ -1051,16 +976,11 @@ func (s *IssueService) UpdateWorklogRecordWithContext(ctx context.Context, issue return responseRecord, resp, nil } -// UpdateWorklogRecord wraps UpdateWorklogRecordWithContext using the background context. -func (s *IssueService) UpdateWorklogRecord(issueID, worklogID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { - return s.UpdateWorklogRecordWithContext(context.Background(), issueID, worklogID, record, options...) -} - -// AddLinkWithContext adds a link between two issues. +// AddLink adds a link between two issues. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issueLink // Caller must close resp.Body -func (s *IssueService) AddLinkWithContext(ctx context.Context, issueLink *IssueLink) (*Response, error) { +func (s *IssueService) AddLink(ctx context.Context, issueLink *IssueLink) (*Response, error) { apiEndpoint := "rest/api/2/issueLink" req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, issueLink) if err != nil { @@ -1075,16 +995,10 @@ func (s *IssueService) AddLinkWithContext(ctx context.Context, issueLink *IssueL return resp, err } -// AddLink wraps AddLinkWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) AddLink(issueLink *IssueLink) (*Response, error) { - return s.AddLinkWithContext(context.Background(), issueLink) -} - -// SearchWithContext will search for tickets according to the jql +// Search 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) Search(ctx context.Context, jql string, options *SearchOptions) ([]Issue, *Response, error) { u := url.URL{ Path: "rest/api/2/search", } @@ -1126,15 +1040,10 @@ func (s *IssueService) SearchWithContext(ctx context.Context, jql string, option return v.Issues, resp, err } -// Search wraps SearchWithContext using the background context. -func (s *IssueService) Search(jql string, options *SearchOptions) ([]Issue, *Response, error) { - return s.SearchWithContext(context.Background(), jql, options) -} - -// SearchPagesWithContext will get issues from all pages in a search +// SearchPages will get issues from all pages in a search // // 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) SearchPagesWithContext(ctx context.Context, jql string, options *SearchOptions, f func(Issue) error) error { +func (s *IssueService) SearchPages(ctx context.Context, jql string, options *SearchOptions, f func(Issue) error) error { if options == nil { options = &SearchOptions{ StartAt: 0, @@ -1146,7 +1055,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.Search(ctx, jql, options) if err != nil { return err } @@ -1168,20 +1077,15 @@ 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.Search(ctx, jql, options) if err != nil { return err } } } -// SearchPages wraps SearchPagesWithContext using the background context. -func (s *IssueService) SearchPages(jql string, options *SearchOptions, f func(Issue) error) error { - return s.SearchPagesWithContext(context.Background(), jql, options, f) -} - -// GetCustomFieldsWithContext returns a map of customfield_* keys with string values -func (s *IssueService) GetCustomFieldsWithContext(ctx context.Context, issueID string) (CustomFields, *Response, error) { +// GetCustomFields returns a map of customfield_* keys with string values +func (s *IssueService) GetCustomFields(ctx context.Context, issueID string) (CustomFields, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -1217,16 +1121,11 @@ func (s *IssueService) GetCustomFieldsWithContext(ctx context.Context, issueID s return cf, resp, nil } -// GetCustomFields wraps GetCustomFieldsWithContext using the background context. -func (s *IssueService) GetCustomFields(issueID string) (CustomFields, *Response, error) { - return s.GetCustomFieldsWithContext(context.Background(), issueID) -} - -// GetTransitionsWithContext gets a list of the transitions possible for this issue by the current user, +// GetTransitions gets a list of the transitions possible for this issue by the current user, // along with fields that are required and their types. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getTransitions -func (s *IssueService) GetTransitionsWithContext(ctx context.Context, id string) ([]Transition, *Response, error) { +func (s *IssueService) GetTransitions(ctx context.Context, id string) ([]Transition, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions?expand=transitions.fields", id) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -1241,35 +1140,25 @@ func (s *IssueService) GetTransitionsWithContext(ctx context.Context, id string) return result.Transitions, resp, err } -// GetTransitions wraps GetTransitionsWithContext using the background context. -func (s *IssueService) GetTransitions(id string) ([]Transition, *Response, error) { - return s.GetTransitionsWithContext(context.Background(), id) -} - -// DoTransitionWithContext performs a transition on an issue. +// DoTransition performs a transition on an issue. // When performing the transition you can update or set other issue fields. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition -func (s *IssueService) DoTransitionWithContext(ctx context.Context, ticketID, transitionID string) (*Response, error) { +func (s *IssueService) DoTransition(ctx context.Context, ticketID, transitionID string) (*Response, error) { payload := CreateTransitionPayload{ Transition: TransitionPayload{ ID: transitionID, }, } - return s.DoTransitionWithPayloadWithContext(ctx, ticketID, payload) -} - -// DoTransition wraps DoTransitionWithContext using the background context. -func (s *IssueService) DoTransition(ticketID, transitionID string) (*Response, error) { - return s.DoTransitionWithContext(context.Background(), ticketID, transitionID) + return s.DoTransitionWithPayload(ctx, ticketID, payload) } -// DoTransitionWithPayloadWithContext performs a transition on an issue using any payload. +// DoTransitionWithPayload performs a transition on an issue using any payload. // When performing the transition you can update or set other issue fields. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition // Caller must close resp.Body -func (s *IssueService) DoTransitionWithPayloadWithContext(ctx context.Context, ticketID, payload interface{}) (*Response, error) { +func (s *IssueService) DoTransitionWithPayload(ctx context.Context, ticketID, payload interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions", ticketID) req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, payload) @@ -1285,12 +1174,6 @@ func (s *IssueService) DoTransitionWithPayloadWithContext(ctx context.Context, t return resp, err } -// DoTransitionWithPayload wraps DoTransitionWithPayloadWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) DoTransitionWithPayload(ticketID, payload interface{}) (*Response, error) { - return s.DoTransitionWithPayloadWithContext(context.Background(), ticketID, payload) -} - // InitIssueWithMetaAndFields returns Issue with with values from fieldsConfig properly set. // - metaProject should contain metaInformation about the project where the issue should be created. // - metaIssuetype is the MetaInformation about the Issuetype that needs to be created. @@ -1372,9 +1255,9 @@ func InitIssueWithMetaAndFields(metaProject *MetaProject, metaIssuetype *MetaIss return issue, nil } -// DeleteWithContext will delete a specified issue. +// Delete will delete a specified issue. // Caller must close resp.Body -func (s *IssueService) DeleteWithContext(ctx context.Context, issueID string) (*Response, error) { +func (s *IssueService) Delete(ctx context.Context, issueID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) // to enable deletion of subtasks; without this, the request will fail if the issue has subtasks @@ -1391,16 +1274,10 @@ func (s *IssueService) DeleteWithContext(ctx context.Context, issueID string) (* return resp, err } -// Delete wraps DeleteWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) Delete(issueID string) (*Response, error) { - return s.DeleteWithContext(context.Background(), issueID) -} - -// GetWatchersWithContext wil return all the users watching/observing the given issue +// GetWatchers wil return all the users watching/observing the given issue // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-getIssueWatchers -func (s *IssueService) GetWatchersWithContext(ctx context.Context, issueID string) (*[]User, *Response, error) { +func (s *IssueService) GetWatchers(ctx context.Context, issueID string) (*[]User, *Response, error) { watchesAPIEndpoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) req, err := s.client.NewRequest(ctx, "GET", watchesAPIEndpoint, nil) @@ -1429,16 +1306,11 @@ func (s *IssueService) GetWatchersWithContext(ctx context.Context, issueID strin return &result, resp, nil } -// GetWatchers wraps GetWatchersWithContext using the background context. -func (s *IssueService) GetWatchers(issueID string) (*[]User, *Response, error) { - return s.GetWatchersWithContext(context.Background(), issueID) -} - -// AddWatcherWithContext adds watcher to the given issue +// AddWatcher adds watcher to the given issue // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-addWatcher // Caller must close resp.Body -func (s *IssueService) AddWatcherWithContext(ctx context.Context, issueID string, userName string) (*Response, error) { +func (s *IssueService) AddWatcher(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, userName) @@ -1454,17 +1326,11 @@ func (s *IssueService) AddWatcherWithContext(ctx context.Context, issueID string return resp, err } -// AddWatcher wraps AddWatcherWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) AddWatcher(issueID string, userName string) (*Response, error) { - return s.AddWatcherWithContext(context.Background(), issueID, userName) -} - -// RemoveWatcherWithContext removes given user from given issue +// RemoveWatcher removes given user from given issue // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-removeWatcher // Caller must close resp.Body -func (s *IssueService) RemoveWatcherWithContext(ctx context.Context, issueID string, userName string) (*Response, error) { +func (s *IssueService) RemoveWatcher(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, userName) @@ -1480,17 +1346,11 @@ func (s *IssueService) RemoveWatcherWithContext(ctx context.Context, issueID str return resp, err } -// RemoveWatcher wraps RemoveWatcherWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) RemoveWatcher(issueID string, userName string) (*Response, error) { - return s.RemoveWatcherWithContext(context.Background(), issueID, userName) -} - -// UpdateAssigneeWithContext updates the user assigned to work on the given issue +// UpdateAssignee updates the user assigned to work on the given issue // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.2/#api/2/issue-assign // Caller must close resp.Body -func (s *IssueService) UpdateAssigneeWithContext(ctx context.Context, issueID string, assignee *User) (*Response, error) { +func (s *IssueService) UpdateAssignee(ctx context.Context, issueID string, assignee *User) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/assignee", issueID) req, err := s.client.NewRequest(ctx, "PUT", apiEndPoint, assignee) @@ -1506,12 +1366,6 @@ func (s *IssueService) UpdateAssigneeWithContext(ctx context.Context, issueID st return resp, err } -// UpdateAssignee wraps UpdateAssigneeWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) UpdateAssignee(issueID string, assignee *User) (*Response, error) { - return s.UpdateAssigneeWithContext(context.Background(), issueID, assignee) -} - func (c ChangelogHistory) CreatedTime() (time.Time, error) { var t time.Time // Ignore null @@ -1522,10 +1376,10 @@ func (c ChangelogHistory) CreatedTime() (time.Time, error) { return t, err } -// GetRemoteLinksWithContext gets remote issue links on the issue. +// GetRemoteLinks gets remote issue links on the issue. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getRemoteIssueLinks -func (s *IssueService) GetRemoteLinksWithContext(ctx context.Context, id string) (*[]RemoteLink, *Response, error) { +func (s *IssueService) GetRemoteLinks(ctx context.Context, id string) (*[]RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", id) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -1540,16 +1394,10 @@ func (s *IssueService) GetRemoteLinksWithContext(ctx context.Context, id string) return result, resp, err } -// GetRemoteLinks wraps GetRemoteLinksWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) GetRemoteLinks(id string) (*[]RemoteLink, *Response, error) { - return s.GetRemoteLinksWithContext(context.Background(), id) -} - -// AddRemoteLinkWithContext adds a remote link to issueID. +// AddRemoteLink adds a remote link to issueID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-remotelink-post -func (s *IssueService) AddRemoteLinkWithContext(ctx context.Context, issueID string, remotelink *RemoteLink) (*RemoteLink, *Response, error) { +func (s *IssueService) AddRemoteLink(ctx context.Context, issueID string, remotelink *RemoteLink) (*RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", issueID) req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, remotelink) if err != nil { @@ -1566,15 +1414,10 @@ func (s *IssueService) AddRemoteLinkWithContext(ctx context.Context, issueID str return responseRemotelink, resp, nil } -// AddRemoteLink wraps AddRemoteLinkWithContext using the background context. -func (s *IssueService) AddRemoteLink(issueID string, remotelink *RemoteLink) (*RemoteLink, *Response, error) { - return s.AddRemoteLinkWithContext(context.Background(), issueID, remotelink) -} - -// UpdateRemoteLinkWithContext updates a remote issue link by linkID. +// UpdateRemoteLink updates a remote issue link by linkID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-remote-links/#api-rest-api-2-issue-issueidorkey-remotelink-linkid-put -func (s *IssueService) UpdateRemoteLinkWithContext(ctx context.Context, issueID string, linkID int, remotelink *RemoteLink) (*Response, error) { +func (s *IssueService) UpdateRemoteLink(ctx context.Context, issueID string, linkID int, remotelink *RemoteLink) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink/%d", issueID, linkID) req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, remotelink) if err != nil { @@ -1589,8 +1432,3 @@ func (s *IssueService) UpdateRemoteLinkWithContext(ctx context.Context, issueID return resp, nil } - -// UpdateRemoteLink wraps UpdateRemoteLinkWithContext using the background context. -func (s *IssueService) UpdateRemoteLink(issueID string, linkID int, remotelink *RemoteLink) (*Response, error) { - return s.UpdateRemoteLinkWithContext(context.Background(), issueID, linkID, remotelink) -} diff --git a/cloud/issue_test.go b/cloud/issue_test.go index 01c8d7f..ff0e5a8 100644 --- a/cloud/issue_test.go +++ b/cloud/issue_test.go @@ -1,6 +1,7 @@ package cloud import ( + "context" "encoding/json" "fmt" "io" @@ -25,7 +26,7 @@ func TestIssueService_Get_Success(t *testing.T) { fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) - issue, _, err := testClient.Issue.Get("10002", nil) + issue, _, err := testClient.Issue.Get(context.Background(), "10002", nil) if issue == nil { t.Error("Expected issue. Issue is nil") } @@ -47,7 +48,7 @@ func TestIssueService_Get_WithQuerySuccess(t *testing.T) { opt := &GetQueryOptions{ Expand: "foo", } - issue, _, err := testClient.Issue.Get("10002", opt) + issue, _, err := testClient.Issue.Get(context.Background(), "10002", opt) if issue == nil { t.Error("Expected issue. Issue is nil") } @@ -72,7 +73,7 @@ func TestIssueService_Create(t *testing.T) { Description: "example bug report", }, } - issue, _, err := testClient.Issue.Create(i) + issue, _, err := testClient.Issue.Create(context.Background(), i) if issue == nil { t.Error("Expected issue. Issue is nil") } @@ -98,7 +99,7 @@ func TestIssueService_CreateThenGet(t *testing.T) { Created: Time(time.Now()), }, } - issue, _, err := testClient.Issue.Create(i) + issue, _, err := testClient.Issue.Create(context.Background(), i) if issue == nil { t.Error("Expected issue. Issue is nil") } @@ -120,7 +121,7 @@ func TestIssueService_CreateThenGet(t *testing.T) { } }) - issue2, _, err := testClient.Issue.Get("10002", nil) + issue2, _, err := testClient.Issue.Get(context.Background(), "10002", nil) if issue2 == nil { t.Error("Expected issue. Issue is nil") } @@ -145,7 +146,7 @@ func TestIssueService_Update(t *testing.T) { Description: "example bug report", }, } - issue, _, err := testClient.Issue.Update(i) + issue, _, err := testClient.Issue.Update(context.Background(), i) if issue == nil { t.Error("Expected issue. Issue is nil") } @@ -167,7 +168,7 @@ func TestIssueService_UpdateIssue(t *testing.T) { i := make(map[string]interface{}) fields := make(map[string]interface{}) i["fields"] = fields - resp, err := testClient.Issue.UpdateIssue(jID, i) + resp, err := testClient.Issue.UpdateIssue(context.Background(), jID, i) if resp == nil { t.Error("Expected resp. resp is nil") } @@ -195,7 +196,7 @@ func TestIssueService_AddComment(t *testing.T) { Value: "Administrators", }, } - comment, _, err := testClient.Issue.AddComment("10000", c) + comment, _, err := testClient.Issue.AddComment(context.Background(), "10000", c) if comment == nil { t.Error("Expected Comment. Comment is nil") } @@ -223,7 +224,7 @@ func TestIssueService_UpdateComment(t *testing.T) { Value: "Administrators", }, } - comment, _, err := testClient.Issue.UpdateComment("10000", c) + comment, _, err := testClient.Issue.UpdateComment(context.Background(), "10000", c) if comment == nil { t.Error("Expected Comment. Comment is nil") } @@ -243,7 +244,7 @@ func TestIssueService_DeleteComment(t *testing.T) { fmt.Fprint(w, `{}`) }) - err := testClient.Issue.DeleteComment("10000", "10001") + err := testClient.Issue.DeleteComment(context.Background(), "10000", "10001") if err != nil { t.Errorf("Error given: %s", err) } @@ -262,7 +263,7 @@ func TestIssueService_AddWorklogRecord(t *testing.T) { r := &WorklogRecord{ TimeSpent: "1h", } - record, _, err := testClient.Issue.AddWorklogRecord("10000", r) + record, _, err := testClient.Issue.AddWorklogRecord(context.Background(), "10000", r) if record == nil { t.Error("Expected Record. Record is nil") } @@ -284,7 +285,7 @@ func TestIssueService_UpdateWorklogRecord(t *testing.T) { r := &WorklogRecord{ TimeSpent: "1h", } - record, _, err := testClient.Issue.UpdateWorklogRecord("10000", "1", r) + record, _, err := testClient.Issue.UpdateWorklogRecord(context.Background(), "10000", "1", r) if record == nil { t.Error("Expected Record. Record is nil") } @@ -321,7 +322,7 @@ func TestIssueService_AddLink(t *testing.T) { }, }, } - resp, err := testClient.Issue.AddLink(il) + resp, err := testClient.Issue.AddLink(context.Background(), il) if err != nil { t.Errorf("Error given: %s", err) } @@ -344,7 +345,7 @@ func TestIssueService_Get_Fields(t *testing.T) { fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) - issue, _, err := testClient.Issue.Get("10002", nil) + issue, _, err := testClient.Issue.Get(context.Background(), "10002", nil) if err != nil { t.Errorf("Error given: %s", err) } @@ -374,7 +375,7 @@ func TestIssueService_Get_RenderedFields(t *testing.T) { fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{},"renderedFields":{"resolutiondate":"In 1 week","updated":"2 hours ago","comment":{"comments":[{"body":"This is HTML"}]}}}`) }) - issue, _, err := testClient.Issue.Get("10002", nil) + issue, _, err := testClient.Issue.Get(context.Background(), "10002", nil) if err != nil { t.Errorf("Error given: %s", err) } @@ -408,7 +409,7 @@ func TestIssueService_DownloadAttachment(t *testing.T) { w.Write([]byte(testAttachment)) }) - resp, err := testClient.Issue.DownloadAttachment("10000") + resp, err := testClient.Issue.DownloadAttachment(context.Background(), "10000") if err != nil { t.Errorf("Error given: %s", err) } @@ -442,7 +443,7 @@ func TestIssueService_DownloadAttachment_BadStatus(t *testing.T) { w.WriteHeader(http.StatusForbidden) }) - resp, err := testClient.Issue.DownloadAttachment("10000") + resp, err := testClient.Issue.DownloadAttachment(context.Background(), "10000") if resp == nil { t.Error("Expected response. Response is nil") return @@ -491,7 +492,7 @@ func TestIssueService_PostAttachment(t *testing.T) { reader := strings.NewReader(testAttachment) - issue, resp, err := testClient.Issue.PostAttachment("10000", reader, "attachment") + issue, resp, err := testClient.Issue.PostAttachment(context.Background(), "10000", reader, "attachment") if issue == nil { t.Error("Expected response. Response is nil") @@ -518,7 +519,7 @@ func TestIssueService_PostAttachment_NoResponse(t *testing.T) { }) reader := strings.NewReader(testAttachment) - _, _, err := testClient.Issue.PostAttachment("10000", reader, "attachment") + _, _, err := testClient.Issue.PostAttachment(context.Background(), "10000", reader, "attachment") if err == nil { t.Errorf("Error expected: %s", err) @@ -538,7 +539,7 @@ func TestIssueService_PostAttachment_NoFilename(t *testing.T) { }) reader := strings.NewReader(testAttachment) - _, _, err := testClient.Issue.PostAttachment("10000", reader, "") + _, _, err := testClient.Issue.PostAttachment(context.Background(), "10000", reader, "") if err != nil { t.Errorf("Error expected: %s", err) @@ -555,7 +556,7 @@ func TestIssueService_PostAttachment_NoAttachment(t *testing.T) { fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) }) - _, _, err := testClient.Issue.PostAttachment("10000", nil, "attachment") + _, _, err := testClient.Issue.PostAttachment(context.Background(), "10000", nil, "attachment") if err != nil { t.Errorf("Error given: %s", err) @@ -573,7 +574,7 @@ func TestIssueService_DeleteAttachment(t *testing.T) { fmt.Fprint(w, `{}`) }) - resp, err := testClient.Issue.DeleteAttachment("10054") + resp, err := testClient.Issue.DeleteAttachment(context.Background(), "10054") if resp.StatusCode != 204 { t.Error("Expected attachment not deleted.") if resp.StatusCode == 403 { @@ -600,7 +601,7 @@ func TestIssueService_DeleteLink(t *testing.T) { fmt.Fprint(w, `{}`) }) - resp, err := testClient.Issue.DeleteLink("10054") + resp, err := testClient.Issue.DeleteLink(context.Background(), "10054") if resp.StatusCode != 204 { t.Error("Expected link not deleted.") if resp.StatusCode == 403 { @@ -627,7 +628,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(context.Background(), "type = Bug and Status NOT IN (Resolved)", opt) if resp == nil { t.Errorf("Response given: %+v", resp) @@ -658,7 +659,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(context.Background(), "", opt) if resp == nil { t.Errorf("Response given: %+v", resp) @@ -687,7 +688,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(context.Background(), "something", nil) if resp == nil { t.Errorf("Response given: %+v", resp) @@ -731,7 +732,7 @@ func TestIssueService_SearchPages(t *testing.T) { opt := &SearchOptions{StartAt: 1, MaxResults: 2, Expand: "foo", ValidateQuery: "warn"} issues := make([]Issue, 0) - err := testClient.Issue.SearchPages("something", opt, func(issue Issue) error { + err := testClient.Issue.SearchPages(context.Background(), "something", opt, func(issue Issue) error { issues = append(issues, issue) return nil }) @@ -762,7 +763,7 @@ func TestIssueService_SearchPages_EmptyResult(t *testing.T) { opt := &SearchOptions{StartAt: 1, MaxResults: 50, Expand: "foo", ValidateQuery: "warn"} issues := make([]Issue, 0) - err := testClient.Issue.SearchPages("something", opt, func(issue Issue) error { + err := testClient.Issue.SearchPages(context.Background(), "something", opt, func(issue Issue) error { issues = append(issues, issue) return nil }) @@ -782,7 +783,7 @@ func TestIssueService_GetCustomFields(t *testing.T) { fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"customfield_123":"test","watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) - issue, _, err := testClient.Issue.GetCustomFields("10002") + issue, _, err := testClient.Issue.GetCustomFields(context.Background(), "10002") if err != nil { t.Errorf("Error given: %s", err) } @@ -804,7 +805,7 @@ func TestIssueService_GetComplexCustomFields(t *testing.T) { fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"customfield_123":{"self":"http://www.example.com/jira/rest/api/2/customFieldOption/123","value":"test","id":"123"},"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) - issue, _, err := testClient.Issue.GetCustomFields("10002") + issue, _, err := testClient.Issue.GetCustomFields(context.Background(), "10002") if err != nil { t.Errorf("Error given: %s", err) } @@ -834,7 +835,7 @@ func TestIssueService_GetTransitions(t *testing.T) { fmt.Fprint(w, string(raw)) }) - transitions, _, err := testClient.Issue.GetTransitions("123") + transitions, _, err := testClient.Issue.GetTransitions(context.Background(), "123") if err != nil { t.Errorf("Got error: %v", err) @@ -876,7 +877,7 @@ func TestIssueService_DoTransition(t *testing.T) { t.Errorf("Expected %s to be in payload, got %s instead", transitionID, payload.Transition.ID) } }) - _, err := testClient.Issue.DoTransition("123", transitionID) + _, err := testClient.Issue.DoTransition(context.Background(), "123", transitionID) if err != nil { t.Errorf("Got error: %v", err) @@ -935,7 +936,7 @@ func TestIssueService_DoTransitionWithPayload(t *testing.T) { t.Errorf("Expected %s to be in payload, got %s instead", transitionID, transition["id"]) } }) - _, err := testClient.Issue.DoTransitionWithPayload("123", customPayload) + _, err := testClient.Issue.DoTransitionWithPayload(context.Background(), "123", customPayload) if err != nil { t.Errorf("Got error: %v", err) @@ -1449,7 +1450,7 @@ func TestIssueService_Delete(t *testing.T) { fmt.Fprint(w, `{}`) }) - resp, err := testClient.Issue.Delete("10002") + resp, err := testClient.Issue.Delete(context.Background(), "10002") if resp.StatusCode != 204 { t.Error("Expected issue not deleted.") } @@ -1567,9 +1568,9 @@ func TestIssueService_GetWorklogs(t *testing.T) { var err error if tc.option != nil { - worklog, _, err = testClient.Issue.GetWorklogs(tc.issueId, WithQueryOptions(tc.option)) + worklog, _, err = testClient.Issue.GetWorklogs(context.Background(), tc.issueId, WithQueryOptions(tc.option)) } else { - worklog, _, err = testClient.Issue.GetWorklogs(tc.issueId) + worklog, _, err = testClient.Issue.GetWorklogs(context.Background(), tc.issueId) } if err != nil && !cmp.Equal(err, tc.err) { @@ -1606,7 +1607,7 @@ func TestIssueService_GetWatchers(t *testing.T) { }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) }) - watchers, _, err := testClient.Issue.GetWatchers("10002") + watchers, _, err := testClient.Issue.GetWatchers(context.Background(), "10002") if err != nil { t.Errorf("Error given: %s", err) return @@ -1646,7 +1647,7 @@ func TestIssueService_DeprecatedGetWatchers(t *testing.T) { }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) }) - watchers, _, err := testClient.Issue.GetWatchers("10002") + watchers, _, err := testClient.Issue.GetWatchers(context.Background(), "10002") if err != nil { t.Errorf("Error given: %s", err) return @@ -1674,7 +1675,7 @@ func TestIssueService_UpdateAssignee(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - resp, err := testClient.Issue.UpdateAssignee("10002", &User{ + resp, err := testClient.Issue.UpdateAssignee(context.Background(), "10002", &User{ Name: "test-username", }) @@ -1696,7 +1697,7 @@ func TestIssueService_Get_Fields_Changelog(t *testing.T) { fmt.Fprint(w, `{"expand":"changelog","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","changelog":{"startAt": 0,"maxResults": 1, "total": 1, "histories": [{"id": "10002", "author": {"self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "key": "fred", "emailAddress": "fred@example.com", "avatarUrls": {"48x48": "http://www.example.com/secure/useravatar?ownerId=fred&avatarId=33072", "24x24": "http://www.example.com/secure/useravatar?size=small&ownerId=fred&avatarId=33072", "16x16": "http://www.example.com/secure/useravatar?size=xsmall&ownerId=fred&avatarId=33072", "32x32": "http://www.example.com/secure/useravatar?size=medium&ownerId=fred&avatarId=33072"},"displayName":"Fred","active": true,"timeZone":"Australia/Sydney"},"created":"2018-06-20T16:50:35.000+0300","items":[{"field":"Rank","fieldtype":"custom","from":"","fromString":"","to":"","toString":"Ranked higher"}]}]}}`) }) - issue, _, _ := testClient.Issue.Get("10002", &GetQueryOptions{Expand: "changelog"}) + issue, _, _ := testClient.Issue.Get(context.Background(), "10002", &GetQueryOptions{Expand: "changelog"}) if issue == nil { t.Error("Expected issue. Issue is nil") return @@ -1727,7 +1728,7 @@ func TestIssueService_Get_Transitions(t *testing.T) { fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/api/latest/issue/10002","key":"EX-1","transitions":[{"id":"121","name":"Start","to":{"self":"http://www.example.com/rest/api/2/status/10444","description":"","iconUrl":"http://www.example.com/images/icons/statuses/inprogress.png","name":"In progress","id":"10444","statusCategory":{"self":"http://www.example.com/rest/api/2/statuscategory/4","id":4,"key":"indeterminate","colorName":"yellow","name":"In Progress"}}}]}`) }) - issue, _, _ := testClient.Issue.Get("10002", &GetQueryOptions{Expand: "transitions"}) + issue, _, _ := testClient.Issue.Get(context.Background(), "10002", &GetQueryOptions{Expand: "transitions"}) if issue == nil { t.Error("Expected issue. Issue is nil") return @@ -1758,7 +1759,7 @@ func TestIssueService_Get_Fields_AffectsVersions(t *testing.T) { fmt.Fprint(w, `{"fields":{"versions":[{"self":"http://www.example.com/jira/rest/api/2/version/10705","id":"10705","description":"test description","name":"2.1.0-rc3","archived":false,"released":false,"releaseDate":"2018-09-30"}]}}`) }) - issue, _, err := testClient.Issue.Get("10002", nil) + issue, _, err := testClient.Issue.Get(context.Background(), "10002", nil) if err != nil { t.Errorf("Error given: %s", err) } @@ -1798,7 +1799,7 @@ func TestIssueService_GetRemoteLinks(t *testing.T) { fmt.Fprint(w, string(raw)) }) - remoteLinks, _, err := testClient.Issue.GetRemoteLinks("123") + remoteLinks, _, err := testClient.Issue.GetRemoteLinks(context.Background(), "123") if err != nil { t.Errorf("Got error: %v", err) } @@ -1852,7 +1853,7 @@ func TestIssueService_AddRemoteLink(t *testing.T) { }, }, } - record, _, err := testClient.Issue.AddRemoteLink("10000", r) + record, _, err := testClient.Issue.AddRemoteLink(context.Background(), "10000", r) if record == nil { t.Error("Expected Record. Record is nil") } @@ -1895,7 +1896,7 @@ func TestIssueService_UpdateRemoteLink(t *testing.T) { }, }, } - _, err := testClient.Issue.UpdateRemoteLink("100", 200, r) + _, err := testClient.Issue.UpdateRemoteLink(context.Background(), "100", 200, r) if err != nil { t.Errorf("Error given: %s", err) } diff --git a/onpremise/auth_transport_jwt_test.go b/onpremise/auth_transport_jwt_test.go index 6332d54..b74cae0 100644 --- a/onpremise/auth_transport_jwt_test.go +++ b/onpremise/auth_transport_jwt_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "net/http" "strings" "testing" @@ -27,5 +28,5 @@ func TestJWTAuthTransport_HeaderContainsJWT(t *testing.T) { }) jwtClient, _ := NewClient(testServer.URL, jwtTransport.Client()) - jwtClient.Issue.Get("TEST-1", nil) + jwtClient.Issue.Get(context.Background(), "TEST-1", nil) } diff --git a/onpremise/examples/addlabel/main.go b/onpremise/examples/addlabel/main.go index 64c2c4d..94e0673 100644 --- a/onpremise/examples/addlabel/main.go +++ b/onpremise/examples/addlabel/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "context" "fmt" "io" "os" @@ -62,7 +63,7 @@ func main() { }, } - resp, err := client.Issue.UpdateIssue(issueId, c) + resp, err := client.Issue.UpdateIssue(context.Background(), issueId, c) if err != nil { fmt.Println(err) @@ -70,7 +71,7 @@ func main() { body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) - issue, _, _ := client.Issue.Get(issueId, nil) + issue, _, _ := client.Issue.Get(context.Background(), issueId, nil) fmt.Printf("Issue: %s:%s\n", issue.Key, issue.Fields.Summary) fmt.Printf("\tLabels: %+v\n", issue.Fields.Labels) diff --git a/onpremise/examples/create/main.go b/onpremise/examples/create/main.go index e1b8d60..8134703 100644 --- a/onpremise/examples/create/main.go +++ b/onpremise/examples/create/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "context" "fmt" "os" "strings" @@ -54,7 +55,7 @@ func main() { }, } - issue, _, err := client.Issue.Create(&i) + issue, _, err := client.Issue.Create(context.Background(), &i) if err != nil { panic(err) } diff --git a/onpremise/examples/createwithcustomfields/main.go b/onpremise/examples/createwithcustomfields/main.go index 3d6e510..71cf096 100644 --- a/onpremise/examples/createwithcustomfields/main.go +++ b/onpremise/examples/createwithcustomfields/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "context" "fmt" "os" "strings" @@ -65,7 +66,7 @@ func main() { }, } - issue, _, err := client.Issue.Create(&i) + issue, _, err := client.Issue.Create(context.Background(), &i) if err != nil { panic(err) } diff --git a/onpremise/examples/ignorecerts/main.go b/onpremise/examples/ignorecerts/main.go index c1ccc14..037603a 100644 --- a/onpremise/examples/ignorecerts/main.go +++ b/onpremise/examples/ignorecerts/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "crypto/tls" "fmt" "net/http" @@ -15,7 +16,7 @@ func main() { client := &http.Client{Transport: tr} jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", client) - issue, _, _ := jiraClient.Issue.Get("MESOS-3325", nil) + issue, _, _ := jiraClient.Issue.Get(context.Background(), "MESOS-3325", nil) fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary) fmt.Printf("Type: %s\n", issue.Fields.Type.Name) diff --git a/onpremise/examples/jql/main.go b/onpremise/examples/jql/main.go index dfb65dc..3c619dc 100644 --- a/onpremise/examples/jql/main.go +++ b/onpremise/examples/jql/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" jira "github.com/andygrunwald/go-jira/onpremise" @@ -13,7 +14,7 @@ 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, err := jiraClient.Issue.Search(context.Background(), jql, nil) if err != nil { panic(err) } @@ -25,7 +26,7 @@ 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, err = jiraClient.Issue.Search(context.Background(), jql, nil) if err != nil { panic(err) } diff --git a/onpremise/examples/newclient/main.go b/onpremise/examples/newclient/main.go index 17bba15..96b1aa6 100644 --- a/onpremise/examples/newclient/main.go +++ b/onpremise/examples/newclient/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" jira "github.com/andygrunwald/go-jira/onpremise" @@ -8,7 +9,7 @@ import ( func main() { jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", nil) - issue, _, _ := jiraClient.Issue.Get("MESOS-3325", nil) + issue, _, _ := jiraClient.Issue.Get(context.Background(), "MESOS-3325", nil) fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary) fmt.Printf("Type: %s\n", issue.Fields.Type.Name) diff --git a/onpremise/examples/pagination/main.go b/onpremise/examples/pagination/main.go index ab2bb36..e8ae219 100644 --- a/onpremise/examples/pagination/main.go +++ b/onpremise/examples/pagination/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" jira "github.com/andygrunwald/go-jira/onpremise" @@ -19,7 +20,7 @@ func GetAllIssues(client *jira.Client, searchString string) ([]jira.Issue, error StartAt: last, } - chunk, resp, err := client.Issue.Search(searchString, opt) + chunk, resp, err := client.Issue.Search(context.Background(), searchString, opt) if err != nil { return nil, err } diff --git a/onpremise/examples/renderedfields/main.go b/onpremise/examples/renderedfields/main.go index e979b47..e9e8118 100644 --- a/onpremise/examples/renderedfields/main.go +++ b/onpremise/examples/renderedfields/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "context" "fmt" "net/http" "os" @@ -52,7 +53,7 @@ func main() { fmt.Printf("Targeting %s for issue %s\n", strings.TrimSpace(jiraURL), key) options := &jira.GetQueryOptions{Expand: "renderedFields"} - u, _, err := client.Issue.Get(key, options) + u, _, err := client.Issue.Get(context.Background(), key, options) if err != nil { fmt.Printf("\n==> error: %v\n", err) diff --git a/onpremise/examples/searchpages/main.go b/onpremise/examples/searchpages/main.go index 82bb3ec..d525512 100644 --- a/onpremise/examples/searchpages/main.go +++ b/onpremise/examples/searchpages/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "context" "fmt" "log" "os" @@ -49,7 +50,7 @@ func main() { // SearchPages will page through results and pass each issue to appendFunc // In this example, we'll search for all the issues in the target project - err = client.Issue.SearchPages(fmt.Sprintf(`project=%s`, strings.TrimSpace(jiraPK)), nil, appendFunc) + err = client.Issue.SearchPages(context.Background(), fmt.Sprintf(`project=%s`, strings.TrimSpace(jiraPK)), nil, appendFunc) if err != nil { log.Fatal(err) } diff --git a/onpremise/issue.go b/onpremise/issue.go index f4b54d9..dee42b9 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -605,7 +605,7 @@ type RemoteLinkStatus struct { Icon *RemoteLinkIcon `json:"icon,omitempty" structs:"icon,omitempty"` } -// GetWithContext returns a full representation of the issue for the given issue key. +// Get returns a full representation of the issue for the given issue key. // Jira will attempt to identify the issue by the issueIdOrKey path parameter. // This can be an issue id, or an issue key. // If the issue cannot be found via an exact match, Jira will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved. @@ -613,7 +613,7 @@ type RemoteLinkStatus struct { // # The given options will be appended to the query string // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getIssue -func (s *IssueService) GetWithContext(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { +func (s *IssueService) Get(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -638,16 +638,11 @@ func (s *IssueService) GetWithContext(ctx context.Context, issueID string, optio return issue, resp, nil } -// Get wraps GetWithContext using the background context. -func (s *IssueService) Get(issueID string, options *GetQueryOptions) (*Issue, *Response, error) { - return s.GetWithContext(context.Background(), issueID, options) -} - -// DownloadAttachmentWithContext returns a Response of an attachment for a given attachmentID. +// DownloadAttachment returns a Response of an attachment for a given attachmentID. // The attachment is in the Response.Body of the response. // This is an io.ReadCloser. // Caller must close resp.Body. -func (s *IssueService) DownloadAttachmentWithContext(ctx context.Context, attachmentID string) (*Response, error) { +func (s *IssueService) DownloadAttachment(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("secure/attachment/%s/", attachmentID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -663,14 +658,8 @@ func (s *IssueService) DownloadAttachmentWithContext(ctx context.Context, attach return resp, nil } -// DownloadAttachment wraps DownloadAttachmentWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) DownloadAttachment(attachmentID string) (*Response, error) { - return s.DownloadAttachmentWithContext(context.Background(), attachmentID) -} - -// PostAttachmentWithContext uploads r (io.Reader) as an attachment to a given issueID -func (s *IssueService) PostAttachmentWithContext(ctx context.Context, issueID string, r io.Reader, attachmentName string) (*[]Attachment, *Response, error) { +// PostAttachment uploads r (io.Reader) as an attachment to a given issueID +func (s *IssueService) PostAttachment(ctx context.Context, issueID string, r io.Reader, attachmentName string) (*[]Attachment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/attachments", issueID) b := new(bytes.Buffer) @@ -707,14 +696,9 @@ func (s *IssueService) PostAttachmentWithContext(ctx context.Context, issueID st return attachment, resp, nil } -// PostAttachment wraps PostAttachmentWithContext using the background context. -func (s *IssueService) PostAttachment(issueID string, r io.Reader, attachmentName string) (*[]Attachment, *Response, error) { - return s.PostAttachmentWithContext(context.Background(), issueID, r, attachmentName) -} - -// DeleteAttachmentWithContext deletes an attachment of a given attachmentID +// DeleteAttachment deletes an attachment of a given attachmentID // Caller must close resp.Body -func (s *IssueService) DeleteAttachmentWithContext(ctx context.Context, attachmentID string) (*Response, error) { +func (s *IssueService) DeleteAttachment(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/attachment/%s", attachmentID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) @@ -731,15 +715,9 @@ func (s *IssueService) DeleteAttachmentWithContext(ctx context.Context, attachme return resp, nil } -// DeleteAttachment wraps DeleteAttachmentWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) DeleteAttachment(attachmentID string) (*Response, error) { - return s.DeleteAttachmentWithContext(context.Background(), attachmentID) -} - -// DeleteLinkWithContext deletes a link of a given linkID +// DeleteLink deletes a link of a given linkID // Caller must close resp.Body -func (s *IssueService) DeleteLinkWithContext(ctx context.Context, linkID string) (*Response, error) { +func (s *IssueService) DeleteLink(ctx context.Context, linkID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLink/%s", linkID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) @@ -756,17 +734,11 @@ func (s *IssueService) DeleteLinkWithContext(ctx context.Context, linkID string) return resp, nil } -// DeleteLink wraps DeleteLinkWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) DeleteLink(linkID string) (*Response, error) { - return s.DeleteLinkWithContext(context.Background(), linkID) -} - -// GetWorklogsWithContext gets all the worklogs for an issue. +// GetWorklogs gets all the worklogs for an issue. // This method is especially important if you need to read all the worklogs, not just the first page. // // https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/worklog-getIssueWorklog -func (s *IssueService) GetWorklogsWithContext(ctx context.Context, issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) { +func (s *IssueService) GetWorklogs(ctx context.Context, issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) @@ -786,11 +758,6 @@ func (s *IssueService) GetWorklogsWithContext(ctx context.Context, issueID strin return v, resp, err } -// GetWorklogs wraps GetWorklogsWithContext using the background context. -func (s *IssueService) GetWorklogs(issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) { - return s.GetWorklogsWithContext(context.Background(), issueID, options...) -} - // Applies query options to http request. // This helper is meant to be used with all "QueryOptions" structs. func WithQueryOptions(options interface{}) func(*http.Request) error { @@ -807,12 +774,12 @@ func WithQueryOptions(options interface{}) func(*http.Request) error { } } -// CreateWithContext creates an issue or a sub-task from a JSON representation. +// Create creates an issue or a sub-task from a JSON representation. // Creating a sub-task is similar to creating a regular issue, with two important differences: // The issueType field must correspond to a sub-task issue type and you must provide a parent field in the issue create request containing the id or key of the parent issue. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-createIssues -func (s *IssueService) CreateWithContext(ctx context.Context, issue *Issue) (*Issue, *Response, error) { +func (s *IssueService) Create(ctx context.Context, issue *Issue) (*Issue, *Response, error) { apiEndpoint := "rest/api/2/issue" req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, issue) if err != nil { @@ -837,17 +804,12 @@ func (s *IssueService) CreateWithContext(ctx context.Context, issue *Issue) (*Is return responseIssue, resp, nil } -// Create wraps CreateWithContext using the background context. -func (s *IssueService) Create(issue *Issue) (*Issue, *Response, error) { - return s.CreateWithContext(context.Background(), issue) -} - -// UpdateWithOptionsWithContext updates an issue from a JSON representation, +// UpdateWithOptions updates an issue from a JSON representation, // while also specifying query params. The issue is found by key. // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-editIssue // Caller must close resp.Body -func (s *IssueService) UpdateWithOptionsWithContext(ctx context.Context, issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) { +func (s *IssueService) UpdateWithOptions(ctx context.Context, issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", issue.Key) url, err := addOptions(apiEndpoint, opts) if err != nil { @@ -869,29 +831,18 @@ func (s *IssueService) UpdateWithOptionsWithContext(ctx context.Context, issue * return &ret, resp, nil } -// UpdateWithOptions wraps UpdateWithOptionsWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) UpdateWithOptions(issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) { - return s.UpdateWithOptionsWithContext(context.Background(), issue, opts) -} - -// UpdateWithContext updates an issue from a JSON representation. The issue is found by key. +// Update updates an issue from a JSON representation. The issue is found by key. // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-editIssue -func (s *IssueService) UpdateWithContext(ctx context.Context, issue *Issue) (*Issue, *Response, error) { - return s.UpdateWithOptionsWithContext(ctx, issue, nil) +func (s *IssueService) Update(ctx context.Context, issue *Issue) (*Issue, *Response, error) { + return s.UpdateWithOptions(ctx, issue, nil) } -// Update wraps UpdateWithContext using the background context. -func (s *IssueService) Update(issue *Issue) (*Issue, *Response, error) { - return s.UpdateWithContext(context.Background(), issue) -} - -// UpdateIssueWithContext updates an issue from a JSON representation. The issue is found by key. +// UpdateIssue updates an issue from a JSON representation. The issue is found by key. // // https://docs.atlassian.com/jira/REST/7.4.0/#api/2/issue-editIssue // Caller must close resp.Body -func (s *IssueService) UpdateIssueWithContext(ctx context.Context, jiraID string, data map[string]interface{}) (*Response, error) { +func (s *IssueService) UpdateIssue(ctx context.Context, jiraID string, data map[string]interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", jiraID) req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, data) if err != nil { @@ -907,16 +858,10 @@ func (s *IssueService) UpdateIssueWithContext(ctx context.Context, jiraID string return resp, nil } -// UpdateIssue wraps UpdateIssueWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) UpdateIssue(jiraID string, data map[string]interface{}) (*Response, error) { - return s.UpdateIssueWithContext(context.Background(), jiraID, data) -} - -// AddCommentWithContext adds a new comment to issueID. +// AddComment adds a new comment to issueID. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-addComment -func (s *IssueService) AddCommentWithContext(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { +func (s *IssueService) AddComment(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment", issueID) req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, comment) if err != nil { @@ -933,15 +878,10 @@ func (s *IssueService) AddCommentWithContext(ctx context.Context, issueID string return responseComment, resp, nil } -// AddComment wraps AddCommentWithContext using the background context. -func (s *IssueService) AddComment(issueID string, comment *Comment) (*Comment, *Response, error) { - return s.AddCommentWithContext(context.Background(), issueID, comment) -} - -// UpdateCommentWithContext updates the body of a comment, identified by comment.ID, on the issueID. +// UpdateComment updates the body of a comment, identified by comment.ID, on the issueID. // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/comment-updateComment -func (s *IssueService) UpdateCommentWithContext(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { +func (s *IssueService) UpdateComment(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { reqBody := struct { Body string `json:"body"` }{ @@ -962,15 +902,10 @@ func (s *IssueService) UpdateCommentWithContext(ctx context.Context, issueID str return responseComment, resp, nil } -// UpdateComment wraps UpdateCommentWithContext using the background context. -func (s *IssueService) UpdateComment(issueID string, comment *Comment) (*Comment, *Response, error) { - return s.UpdateCommentWithContext(context.Background(), issueID, comment) -} - -// DeleteCommentWithContext Deletes a comment from an issueID. +// DeleteComment Deletes a comment from an issueID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-issue-issueIdOrKey-comment-id-delete -func (s *IssueService) DeleteCommentWithContext(ctx context.Context, issueID, commentID string) error { +func (s *IssueService) DeleteComment(ctx context.Context, issueID, commentID string) error { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, commentID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) if err != nil { @@ -987,15 +922,10 @@ func (s *IssueService) DeleteCommentWithContext(ctx context.Context, issueID, co return nil } -// DeleteComment wraps DeleteCommentWithContext using the background context. -func (s *IssueService) DeleteComment(issueID, commentID string) error { - return s.DeleteCommentWithContext(context.Background(), issueID, commentID) -} - -// AddWorklogRecordWithContext adds a new worklog record to issueID. +// AddWorklogRecord adds a new worklog record to issueID. // // https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-worklog-post -func (s *IssueService) AddWorklogRecordWithContext(ctx context.Context, issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { +func (s *IssueService) AddWorklogRecord(ctx context.Context, issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, record) if err != nil { @@ -1019,15 +949,10 @@ func (s *IssueService) AddWorklogRecordWithContext(ctx context.Context, issueID return responseRecord, resp, nil } -// AddWorklogRecord wraps AddWorklogRecordWithContext using the background context. -func (s *IssueService) AddWorklogRecord(issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { - return s.AddWorklogRecordWithContext(context.Background(), issueID, record, options...) -} - -// UpdateWorklogRecordWithContext updates a worklog record. +// UpdateWorklogRecord updates a worklog record. // // https://docs.atlassian.com/software/jira/docs/api/REST/7.1.2/#api/2/issue-updateWorklog -func (s *IssueService) UpdateWorklogRecordWithContext(ctx context.Context, issueID, worklogID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { +func (s *IssueService) UpdateWorklogRecord(ctx context.Context, issueID, worklogID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog/%s", issueID, worklogID) req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, record) if err != nil { @@ -1051,16 +976,11 @@ func (s *IssueService) UpdateWorklogRecordWithContext(ctx context.Context, issue return responseRecord, resp, nil } -// UpdateWorklogRecord wraps UpdateWorklogRecordWithContext using the background context. -func (s *IssueService) UpdateWorklogRecord(issueID, worklogID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { - return s.UpdateWorklogRecordWithContext(context.Background(), issueID, worklogID, record, options...) -} - -// AddLinkWithContext adds a link between two issues. +// AddLink adds a link between two issues. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issueLink // Caller must close resp.Body -func (s *IssueService) AddLinkWithContext(ctx context.Context, issueLink *IssueLink) (*Response, error) { +func (s *IssueService) AddLink(ctx context.Context, issueLink *IssueLink) (*Response, error) { apiEndpoint := "rest/api/2/issueLink" req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, issueLink) if err != nil { @@ -1075,16 +995,10 @@ func (s *IssueService) AddLinkWithContext(ctx context.Context, issueLink *IssueL return resp, err } -// AddLink wraps AddLinkWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) AddLink(issueLink *IssueLink) (*Response, error) { - return s.AddLinkWithContext(context.Background(), issueLink) -} - -// SearchWithContext will search for tickets according to the jql +// Search 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) Search(ctx context.Context, jql string, options *SearchOptions) ([]Issue, *Response, error) { u := url.URL{ Path: "rest/api/2/search", } @@ -1126,15 +1040,10 @@ func (s *IssueService) SearchWithContext(ctx context.Context, jql string, option return v.Issues, resp, err } -// Search wraps SearchWithContext using the background context. -func (s *IssueService) Search(jql string, options *SearchOptions) ([]Issue, *Response, error) { - return s.SearchWithContext(context.Background(), jql, options) -} - -// SearchPagesWithContext will get issues from all pages in a search +// SearchPages will get issues from all pages in a search // // 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) SearchPagesWithContext(ctx context.Context, jql string, options *SearchOptions, f func(Issue) error) error { +func (s *IssueService) SearchPages(ctx context.Context, jql string, options *SearchOptions, f func(Issue) error) error { if options == nil { options = &SearchOptions{ StartAt: 0, @@ -1146,7 +1055,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.Search(ctx, jql, options) if err != nil { return err } @@ -1168,20 +1077,15 @@ 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.Search(ctx, jql, options) if err != nil { return err } } } -// SearchPages wraps SearchPagesWithContext using the background context. -func (s *IssueService) SearchPages(jql string, options *SearchOptions, f func(Issue) error) error { - return s.SearchPagesWithContext(context.Background(), jql, options, f) -} - -// GetCustomFieldsWithContext returns a map of customfield_* keys with string values -func (s *IssueService) GetCustomFieldsWithContext(ctx context.Context, issueID string) (CustomFields, *Response, error) { +// GetCustomFields returns a map of customfield_* keys with string values +func (s *IssueService) GetCustomFields(ctx context.Context, issueID string) (CustomFields, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -1217,16 +1121,11 @@ func (s *IssueService) GetCustomFieldsWithContext(ctx context.Context, issueID s return cf, resp, nil } -// GetCustomFields wraps GetCustomFieldsWithContext using the background context. -func (s *IssueService) GetCustomFields(issueID string) (CustomFields, *Response, error) { - return s.GetCustomFieldsWithContext(context.Background(), issueID) -} - -// GetTransitionsWithContext gets a list of the transitions possible for this issue by the current user, +// GetTransitions gets a list of the transitions possible for this issue by the current user, // along with fields that are required and their types. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getTransitions -func (s *IssueService) GetTransitionsWithContext(ctx context.Context, id string) ([]Transition, *Response, error) { +func (s *IssueService) GetTransitions(ctx context.Context, id string) ([]Transition, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions?expand=transitions.fields", id) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -1241,35 +1140,25 @@ func (s *IssueService) GetTransitionsWithContext(ctx context.Context, id string) return result.Transitions, resp, err } -// GetTransitions wraps GetTransitionsWithContext using the background context. -func (s *IssueService) GetTransitions(id string) ([]Transition, *Response, error) { - return s.GetTransitionsWithContext(context.Background(), id) -} - -// DoTransitionWithContext performs a transition on an issue. +// DoTransition performs a transition on an issue. // When performing the transition you can update or set other issue fields. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition -func (s *IssueService) DoTransitionWithContext(ctx context.Context, ticketID, transitionID string) (*Response, error) { +func (s *IssueService) DoTransition(ctx context.Context, ticketID, transitionID string) (*Response, error) { payload := CreateTransitionPayload{ Transition: TransitionPayload{ ID: transitionID, }, } - return s.DoTransitionWithPayloadWithContext(ctx, ticketID, payload) -} - -// DoTransition wraps DoTransitionWithContext using the background context. -func (s *IssueService) DoTransition(ticketID, transitionID string) (*Response, error) { - return s.DoTransitionWithContext(context.Background(), ticketID, transitionID) + return s.DoTransitionWithPayload(ctx, ticketID, payload) } -// DoTransitionWithPayloadWithContext performs a transition on an issue using any payload. +// DoTransitionWithPayload performs a transition on an issue using any payload. // When performing the transition you can update or set other issue fields. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition // Caller must close resp.Body -func (s *IssueService) DoTransitionWithPayloadWithContext(ctx context.Context, ticketID, payload interface{}) (*Response, error) { +func (s *IssueService) DoTransitionWithPayload(ctx context.Context, ticketID, payload interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions", ticketID) req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, payload) @@ -1285,12 +1174,6 @@ func (s *IssueService) DoTransitionWithPayloadWithContext(ctx context.Context, t return resp, err } -// DoTransitionWithPayload wraps DoTransitionWithPayloadWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) DoTransitionWithPayload(ticketID, payload interface{}) (*Response, error) { - return s.DoTransitionWithPayloadWithContext(context.Background(), ticketID, payload) -} - // InitIssueWithMetaAndFields returns Issue with with values from fieldsConfig properly set. // - metaProject should contain metaInformation about the project where the issue should be created. // - metaIssuetype is the MetaInformation about the Issuetype that needs to be created. @@ -1372,9 +1255,9 @@ func InitIssueWithMetaAndFields(metaProject *MetaProject, metaIssuetype *MetaIss return issue, nil } -// DeleteWithContext will delete a specified issue. +// Delete will delete a specified issue. // Caller must close resp.Body -func (s *IssueService) DeleteWithContext(ctx context.Context, issueID string) (*Response, error) { +func (s *IssueService) Delete(ctx context.Context, issueID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) // to enable deletion of subtasks; without this, the request will fail if the issue has subtasks @@ -1391,16 +1274,10 @@ func (s *IssueService) DeleteWithContext(ctx context.Context, issueID string) (* return resp, err } -// Delete wraps DeleteWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) Delete(issueID string) (*Response, error) { - return s.DeleteWithContext(context.Background(), issueID) -} - -// GetWatchersWithContext wil return all the users watching/observing the given issue +// GetWatchers wil return all the users watching/observing the given issue // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-getIssueWatchers -func (s *IssueService) GetWatchersWithContext(ctx context.Context, issueID string) (*[]User, *Response, error) { +func (s *IssueService) GetWatchers(ctx context.Context, issueID string) (*[]User, *Response, error) { watchesAPIEndpoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) req, err := s.client.NewRequest(ctx, "GET", watchesAPIEndpoint, nil) @@ -1429,16 +1306,11 @@ func (s *IssueService) GetWatchersWithContext(ctx context.Context, issueID strin return &result, resp, nil } -// GetWatchers wraps GetWatchersWithContext using the background context. -func (s *IssueService) GetWatchers(issueID string) (*[]User, *Response, error) { - return s.GetWatchersWithContext(context.Background(), issueID) -} - -// AddWatcherWithContext adds watcher to the given issue +// AddWatcher adds watcher to the given issue // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-addWatcher // Caller must close resp.Body -func (s *IssueService) AddWatcherWithContext(ctx context.Context, issueID string, userName string) (*Response, error) { +func (s *IssueService) AddWatcher(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, userName) @@ -1454,17 +1326,11 @@ func (s *IssueService) AddWatcherWithContext(ctx context.Context, issueID string return resp, err } -// AddWatcher wraps AddWatcherWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) AddWatcher(issueID string, userName string) (*Response, error) { - return s.AddWatcherWithContext(context.Background(), issueID, userName) -} - -// RemoveWatcherWithContext removes given user from given issue +// RemoveWatcher removes given user from given issue // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-removeWatcher // Caller must close resp.Body -func (s *IssueService) RemoveWatcherWithContext(ctx context.Context, issueID string, userName string) (*Response, error) { +func (s *IssueService) RemoveWatcher(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, userName) @@ -1480,17 +1346,11 @@ func (s *IssueService) RemoveWatcherWithContext(ctx context.Context, issueID str return resp, err } -// RemoveWatcher wraps RemoveWatcherWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) RemoveWatcher(issueID string, userName string) (*Response, error) { - return s.RemoveWatcherWithContext(context.Background(), issueID, userName) -} - -// UpdateAssigneeWithContext updates the user assigned to work on the given issue +// UpdateAssignee updates the user assigned to work on the given issue // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.2/#api/2/issue-assign // Caller must close resp.Body -func (s *IssueService) UpdateAssigneeWithContext(ctx context.Context, issueID string, assignee *User) (*Response, error) { +func (s *IssueService) UpdateAssignee(ctx context.Context, issueID string, assignee *User) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/assignee", issueID) req, err := s.client.NewRequest(ctx, "PUT", apiEndPoint, assignee) @@ -1506,12 +1366,6 @@ func (s *IssueService) UpdateAssigneeWithContext(ctx context.Context, issueID st return resp, err } -// UpdateAssignee wraps UpdateAssigneeWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) UpdateAssignee(issueID string, assignee *User) (*Response, error) { - return s.UpdateAssigneeWithContext(context.Background(), issueID, assignee) -} - func (c ChangelogHistory) CreatedTime() (time.Time, error) { var t time.Time // Ignore null @@ -1522,10 +1376,10 @@ func (c ChangelogHistory) CreatedTime() (time.Time, error) { return t, err } -// GetRemoteLinksWithContext gets remote issue links on the issue. +// GetRemoteLinks gets remote issue links on the issue. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getRemoteIssueLinks -func (s *IssueService) GetRemoteLinksWithContext(ctx context.Context, id string) (*[]RemoteLink, *Response, error) { +func (s *IssueService) GetRemoteLinks(ctx context.Context, id string) (*[]RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", id) req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) if err != nil { @@ -1540,16 +1394,10 @@ func (s *IssueService) GetRemoteLinksWithContext(ctx context.Context, id string) return result, resp, err } -// GetRemoteLinks wraps GetRemoteLinksWithContext using the background context. -// Caller must close resp.Body -func (s *IssueService) GetRemoteLinks(id string) (*[]RemoteLink, *Response, error) { - return s.GetRemoteLinksWithContext(context.Background(), id) -} - -// AddRemoteLinkWithContext adds a remote link to issueID. +// AddRemoteLink adds a remote link to issueID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-remotelink-post -func (s *IssueService) AddRemoteLinkWithContext(ctx context.Context, issueID string, remotelink *RemoteLink) (*RemoteLink, *Response, error) { +func (s *IssueService) AddRemoteLink(ctx context.Context, issueID string, remotelink *RemoteLink) (*RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", issueID) req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, remotelink) if err != nil { @@ -1566,15 +1414,10 @@ func (s *IssueService) AddRemoteLinkWithContext(ctx context.Context, issueID str return responseRemotelink, resp, nil } -// AddRemoteLink wraps AddRemoteLinkWithContext using the background context. -func (s *IssueService) AddRemoteLink(issueID string, remotelink *RemoteLink) (*RemoteLink, *Response, error) { - return s.AddRemoteLinkWithContext(context.Background(), issueID, remotelink) -} - -// UpdateRemoteLinkWithContext updates a remote issue link by linkID. +// UpdateRemoteLink updates a remote issue link by linkID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-remote-links/#api-rest-api-2-issue-issueidorkey-remotelink-linkid-put -func (s *IssueService) UpdateRemoteLinkWithContext(ctx context.Context, issueID string, linkID int, remotelink *RemoteLink) (*Response, error) { +func (s *IssueService) UpdateRemoteLink(ctx context.Context, issueID string, linkID int, remotelink *RemoteLink) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink/%d", issueID, linkID) req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, remotelink) if err != nil { @@ -1589,8 +1432,3 @@ func (s *IssueService) UpdateRemoteLinkWithContext(ctx context.Context, issueID return resp, nil } - -// UpdateRemoteLink wraps UpdateRemoteLinkWithContext using the background context. -func (s *IssueService) UpdateRemoteLink(issueID string, linkID int, remotelink *RemoteLink) (*Response, error) { - return s.UpdateRemoteLinkWithContext(context.Background(), issueID, linkID, remotelink) -} diff --git a/onpremise/issue_test.go b/onpremise/issue_test.go index 5816f67..f1d7c76 100644 --- a/onpremise/issue_test.go +++ b/onpremise/issue_test.go @@ -1,6 +1,7 @@ package onpremise import ( + "context" "encoding/json" "fmt" "io" @@ -25,7 +26,7 @@ func TestIssueService_Get_Success(t *testing.T) { fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) - issue, _, err := testClient.Issue.Get("10002", nil) + issue, _, err := testClient.Issue.Get(context.Background(), "10002", nil) if issue == nil { t.Error("Expected issue. Issue is nil") } @@ -47,7 +48,7 @@ func TestIssueService_Get_WithQuerySuccess(t *testing.T) { opt := &GetQueryOptions{ Expand: "foo", } - issue, _, err := testClient.Issue.Get("10002", opt) + issue, _, err := testClient.Issue.Get(context.Background(), "10002", opt) if issue == nil { t.Error("Expected issue. Issue is nil") } @@ -72,7 +73,7 @@ func TestIssueService_Create(t *testing.T) { Description: "example bug report", }, } - issue, _, err := testClient.Issue.Create(i) + issue, _, err := testClient.Issue.Create(context.Background(), i) if issue == nil { t.Error("Expected issue. Issue is nil") } @@ -98,7 +99,7 @@ func TestIssueService_CreateThenGet(t *testing.T) { Created: Time(time.Now()), }, } - issue, _, err := testClient.Issue.Create(i) + issue, _, err := testClient.Issue.Create(context.Background(), i) if issue == nil { t.Error("Expected issue. Issue is nil") } @@ -120,7 +121,7 @@ func TestIssueService_CreateThenGet(t *testing.T) { } }) - issue2, _, err := testClient.Issue.Get("10002", nil) + issue2, _, err := testClient.Issue.Get(context.Background(), "10002", nil) if issue2 == nil { t.Error("Expected issue. Issue is nil") } @@ -145,7 +146,7 @@ func TestIssueService_Update(t *testing.T) { Description: "example bug report", }, } - issue, _, err := testClient.Issue.Update(i) + issue, _, err := testClient.Issue.Update(context.Background(), i) if issue == nil { t.Error("Expected issue. Issue is nil") } @@ -167,7 +168,7 @@ func TestIssueService_UpdateIssue(t *testing.T) { i := make(map[string]interface{}) fields := make(map[string]interface{}) i["fields"] = fields - resp, err := testClient.Issue.UpdateIssue(jID, i) + resp, err := testClient.Issue.UpdateIssue(context.Background(), jID, i) if resp == nil { t.Error("Expected resp. resp is nil") } @@ -195,7 +196,7 @@ func TestIssueService_AddComment(t *testing.T) { Value: "Administrators", }, } - comment, _, err := testClient.Issue.AddComment("10000", c) + comment, _, err := testClient.Issue.AddComment(context.Background(), "10000", c) if comment == nil { t.Error("Expected Comment. Comment is nil") } @@ -223,7 +224,7 @@ func TestIssueService_UpdateComment(t *testing.T) { Value: "Administrators", }, } - comment, _, err := testClient.Issue.UpdateComment("10000", c) + comment, _, err := testClient.Issue.UpdateComment(context.Background(), "10000", c) if comment == nil { t.Error("Expected Comment. Comment is nil") } @@ -243,7 +244,7 @@ func TestIssueService_DeleteComment(t *testing.T) { fmt.Fprint(w, `{}`) }) - err := testClient.Issue.DeleteComment("10000", "10001") + err := testClient.Issue.DeleteComment(context.Background(), "10000", "10001") if err != nil { t.Errorf("Error given: %s", err) } @@ -262,7 +263,7 @@ func TestIssueService_AddWorklogRecord(t *testing.T) { r := &WorklogRecord{ TimeSpent: "1h", } - record, _, err := testClient.Issue.AddWorklogRecord("10000", r) + record, _, err := testClient.Issue.AddWorklogRecord(context.Background(), "10000", r) if record == nil { t.Error("Expected Record. Record is nil") } @@ -284,7 +285,7 @@ func TestIssueService_UpdateWorklogRecord(t *testing.T) { r := &WorklogRecord{ TimeSpent: "1h", } - record, _, err := testClient.Issue.UpdateWorklogRecord("10000", "1", r) + record, _, err := testClient.Issue.UpdateWorklogRecord(context.Background(), "10000", "1", r) if record == nil { t.Error("Expected Record. Record is nil") } @@ -321,7 +322,7 @@ func TestIssueService_AddLink(t *testing.T) { }, }, } - resp, err := testClient.Issue.AddLink(il) + resp, err := testClient.Issue.AddLink(context.Background(), il) if err != nil { t.Errorf("Error given: %s", err) } @@ -344,7 +345,7 @@ func TestIssueService_Get_Fields(t *testing.T) { fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) - issue, _, err := testClient.Issue.Get("10002", nil) + issue, _, err := testClient.Issue.Get(context.Background(), "10002", nil) if err != nil { t.Errorf("Error given: %s", err) } @@ -374,7 +375,7 @@ func TestIssueService_Get_RenderedFields(t *testing.T) { fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{},"renderedFields":{"resolutiondate":"In 1 week","updated":"2 hours ago","comment":{"comments":[{"body":"This is HTML"}]}}}`) }) - issue, _, err := testClient.Issue.Get("10002", nil) + issue, _, err := testClient.Issue.Get(context.Background(), "10002", nil) if err != nil { t.Errorf("Error given: %s", err) } @@ -408,7 +409,7 @@ func TestIssueService_DownloadAttachment(t *testing.T) { w.Write([]byte(testAttachment)) }) - resp, err := testClient.Issue.DownloadAttachment("10000") + resp, err := testClient.Issue.DownloadAttachment(context.Background(), "10000") if err != nil { t.Errorf("Error given: %s", err) } @@ -442,7 +443,7 @@ func TestIssueService_DownloadAttachment_BadStatus(t *testing.T) { w.WriteHeader(http.StatusForbidden) }) - resp, err := testClient.Issue.DownloadAttachment("10000") + resp, err := testClient.Issue.DownloadAttachment(context.Background(), "10000") if resp == nil { t.Error("Expected response. Response is nil") return @@ -491,7 +492,7 @@ func TestIssueService_PostAttachment(t *testing.T) { reader := strings.NewReader(testAttachment) - issue, resp, err := testClient.Issue.PostAttachment("10000", reader, "attachment") + issue, resp, err := testClient.Issue.PostAttachment(context.Background(), "10000", reader, "attachment") if issue == nil { t.Error("Expected response. Response is nil") @@ -518,7 +519,7 @@ func TestIssueService_PostAttachment_NoResponse(t *testing.T) { }) reader := strings.NewReader(testAttachment) - _, _, err := testClient.Issue.PostAttachment("10000", reader, "attachment") + _, _, err := testClient.Issue.PostAttachment(context.Background(), "10000", reader, "attachment") if err == nil { t.Errorf("Error expected: %s", err) @@ -538,7 +539,7 @@ func TestIssueService_PostAttachment_NoFilename(t *testing.T) { }) reader := strings.NewReader(testAttachment) - _, _, err := testClient.Issue.PostAttachment("10000", reader, "") + _, _, err := testClient.Issue.PostAttachment(context.Background(), "10000", reader, "") if err != nil { t.Errorf("Error expected: %s", err) @@ -555,7 +556,7 @@ func TestIssueService_PostAttachment_NoAttachment(t *testing.T) { fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) }) - _, _, err := testClient.Issue.PostAttachment("10000", nil, "attachment") + _, _, err := testClient.Issue.PostAttachment(context.Background(), "10000", nil, "attachment") if err != nil { t.Errorf("Error given: %s", err) @@ -573,7 +574,7 @@ func TestIssueService_DeleteAttachment(t *testing.T) { fmt.Fprint(w, `{}`) }) - resp, err := testClient.Issue.DeleteAttachment("10054") + resp, err := testClient.Issue.DeleteAttachment(context.Background(), "10054") if resp.StatusCode != 204 { t.Error("Expected attachment not deleted.") if resp.StatusCode == 403 { @@ -600,7 +601,7 @@ func TestIssueService_DeleteLink(t *testing.T) { fmt.Fprint(w, `{}`) }) - resp, err := testClient.Issue.DeleteLink("10054") + resp, err := testClient.Issue.DeleteLink(context.Background(), "10054") if resp.StatusCode != 204 { t.Error("Expected link not deleted.") if resp.StatusCode == 403 { @@ -627,7 +628,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(context.Background(), "type = Bug and Status NOT IN (Resolved)", opt) if resp == nil { t.Errorf("Response given: %+v", resp) @@ -658,7 +659,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(context.Background(), "", opt) if resp == nil { t.Errorf("Response given: %+v", resp) @@ -687,7 +688,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(context.Background(), "something", nil) if resp == nil { t.Errorf("Response given: %+v", resp) @@ -731,7 +732,7 @@ func TestIssueService_SearchPages(t *testing.T) { opt := &SearchOptions{StartAt: 1, MaxResults: 2, Expand: "foo", ValidateQuery: "warn"} issues := make([]Issue, 0) - err := testClient.Issue.SearchPages("something", opt, func(issue Issue) error { + err := testClient.Issue.SearchPages(context.Background(), "something", opt, func(issue Issue) error { issues = append(issues, issue) return nil }) @@ -762,7 +763,7 @@ func TestIssueService_SearchPages_EmptyResult(t *testing.T) { opt := &SearchOptions{StartAt: 1, MaxResults: 50, Expand: "foo", ValidateQuery: "warn"} issues := make([]Issue, 0) - err := testClient.Issue.SearchPages("something", opt, func(issue Issue) error { + err := testClient.Issue.SearchPages(context.Background(), "something", opt, func(issue Issue) error { issues = append(issues, issue) return nil }) @@ -782,7 +783,7 @@ func TestIssueService_GetCustomFields(t *testing.T) { fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"customfield_123":"test","watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) - issue, _, err := testClient.Issue.GetCustomFields("10002") + issue, _, err := testClient.Issue.GetCustomFields(context.Background(), "10002") if err != nil { t.Errorf("Error given: %s", err) } @@ -804,7 +805,7 @@ func TestIssueService_GetComplexCustomFields(t *testing.T) { fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"customfield_123":{"self":"http://www.example.com/jira/rest/api/2/customFieldOption/123","value":"test","id":"123"},"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) - issue, _, err := testClient.Issue.GetCustomFields("10002") + issue, _, err := testClient.Issue.GetCustomFields(context.Background(), "10002") if err != nil { t.Errorf("Error given: %s", err) } @@ -834,7 +835,7 @@ func TestIssueService_GetTransitions(t *testing.T) { fmt.Fprint(w, string(raw)) }) - transitions, _, err := testClient.Issue.GetTransitions("123") + transitions, _, err := testClient.Issue.GetTransitions(context.Background(), "123") if err != nil { t.Errorf("Got error: %v", err) @@ -876,7 +877,7 @@ func TestIssueService_DoTransition(t *testing.T) { t.Errorf("Expected %s to be in payload, got %s instead", transitionID, payload.Transition.ID) } }) - _, err := testClient.Issue.DoTransition("123", transitionID) + _, err := testClient.Issue.DoTransition(context.Background(), "123", transitionID) if err != nil { t.Errorf("Got error: %v", err) @@ -935,7 +936,7 @@ func TestIssueService_DoTransitionWithPayload(t *testing.T) { t.Errorf("Expected %s to be in payload, got %s instead", transitionID, transition["id"]) } }) - _, err := testClient.Issue.DoTransitionWithPayload("123", customPayload) + _, err := testClient.Issue.DoTransitionWithPayload(context.Background(), "123", customPayload) if err != nil { t.Errorf("Got error: %v", err) @@ -1449,7 +1450,7 @@ func TestIssueService_Delete(t *testing.T) { fmt.Fprint(w, `{}`) }) - resp, err := testClient.Issue.Delete("10002") + resp, err := testClient.Issue.Delete(context.Background(), "10002") if resp.StatusCode != 204 { t.Error("Expected issue not deleted.") } @@ -1567,9 +1568,9 @@ func TestIssueService_GetWorklogs(t *testing.T) { var err error if tc.option != nil { - worklog, _, err = testClient.Issue.GetWorklogs(tc.issueId, WithQueryOptions(tc.option)) + worklog, _, err = testClient.Issue.GetWorklogs(context.Background(), tc.issueId, WithQueryOptions(tc.option)) } else { - worklog, _, err = testClient.Issue.GetWorklogs(tc.issueId) + worklog, _, err = testClient.Issue.GetWorklogs(context.Background(), tc.issueId) } if err != nil && !cmp.Equal(err, tc.err) { @@ -1606,7 +1607,7 @@ func TestIssueService_GetWatchers(t *testing.T) { }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) }) - watchers, _, err := testClient.Issue.GetWatchers("10002") + watchers, _, err := testClient.Issue.GetWatchers(context.Background(), "10002") if err != nil { t.Errorf("Error given: %s", err) return @@ -1646,7 +1647,7 @@ func TestIssueService_DeprecatedGetWatchers(t *testing.T) { }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) }) - watchers, _, err := testClient.Issue.GetWatchers("10002") + watchers, _, err := testClient.Issue.GetWatchers(context.Background(), "10002") if err != nil { t.Errorf("Error given: %s", err) return @@ -1674,7 +1675,7 @@ func TestIssueService_UpdateAssignee(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - resp, err := testClient.Issue.UpdateAssignee("10002", &User{ + resp, err := testClient.Issue.UpdateAssignee(context.Background(), "10002", &User{ Name: "test-username", }) @@ -1696,7 +1697,7 @@ func TestIssueService_Get_Fields_Changelog(t *testing.T) { fmt.Fprint(w, `{"expand":"changelog","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","changelog":{"startAt": 0,"maxResults": 1, "total": 1, "histories": [{"id": "10002", "author": {"self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "key": "fred", "emailAddress": "fred@example.com", "avatarUrls": {"48x48": "http://www.example.com/secure/useravatar?ownerId=fred&avatarId=33072", "24x24": "http://www.example.com/secure/useravatar?size=small&ownerId=fred&avatarId=33072", "16x16": "http://www.example.com/secure/useravatar?size=xsmall&ownerId=fred&avatarId=33072", "32x32": "http://www.example.com/secure/useravatar?size=medium&ownerId=fred&avatarId=33072"},"displayName":"Fred","active": true,"timeZone":"Australia/Sydney"},"created":"2018-06-20T16:50:35.000+0300","items":[{"field":"Rank","fieldtype":"custom","from":"","fromString":"","to":"","toString":"Ranked higher"}]}]}}`) }) - issue, _, _ := testClient.Issue.Get("10002", &GetQueryOptions{Expand: "changelog"}) + issue, _, _ := testClient.Issue.Get(context.Background(), "10002", &GetQueryOptions{Expand: "changelog"}) if issue == nil { t.Error("Expected issue. Issue is nil") return @@ -1727,7 +1728,7 @@ func TestIssueService_Get_Transitions(t *testing.T) { fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/api/latest/issue/10002","key":"EX-1","transitions":[{"id":"121","name":"Start","to":{"self":"http://www.example.com/rest/api/2/status/10444","description":"","iconUrl":"http://www.example.com/images/icons/statuses/inprogress.png","name":"In progress","id":"10444","statusCategory":{"self":"http://www.example.com/rest/api/2/statuscategory/4","id":4,"key":"indeterminate","colorName":"yellow","name":"In Progress"}}}]}`) }) - issue, _, _ := testClient.Issue.Get("10002", &GetQueryOptions{Expand: "transitions"}) + issue, _, _ := testClient.Issue.Get(context.Background(), "10002", &GetQueryOptions{Expand: "transitions"}) if issue == nil { t.Error("Expected issue. Issue is nil") return @@ -1758,7 +1759,7 @@ func TestIssueService_Get_Fields_AffectsVersions(t *testing.T) { fmt.Fprint(w, `{"fields":{"versions":[{"self":"http://www.example.com/jira/rest/api/2/version/10705","id":"10705","description":"test description","name":"2.1.0-rc3","archived":false,"released":false,"releaseDate":"2018-09-30"}]}}`) }) - issue, _, err := testClient.Issue.Get("10002", nil) + issue, _, err := testClient.Issue.Get(context.Background(), "10002", nil) if err != nil { t.Errorf("Error given: %s", err) } @@ -1798,7 +1799,7 @@ func TestIssueService_GetRemoteLinks(t *testing.T) { fmt.Fprint(w, string(raw)) }) - remoteLinks, _, err := testClient.Issue.GetRemoteLinks("123") + remoteLinks, _, err := testClient.Issue.GetRemoteLinks(context.Background(), "123") if err != nil { t.Errorf("Got error: %v", err) } @@ -1852,7 +1853,7 @@ func TestIssueService_AddRemoteLink(t *testing.T) { }, }, } - record, _, err := testClient.Issue.AddRemoteLink("10000", r) + record, _, err := testClient.Issue.AddRemoteLink(context.Background(), "10000", r) if record == nil { t.Error("Expected Record. Record is nil") } @@ -1895,7 +1896,7 @@ func TestIssueService_UpdateRemoteLink(t *testing.T) { }, }, } - _, err := testClient.Issue.UpdateRemoteLink("100", 200, r) + _, err := testClient.Issue.UpdateRemoteLink(context.Background(), "100", 200, r) if err != nil { t.Errorf("Error given: %s", err) } From 948e8bc53cfe33e79192f0e39817be0c4f8b5daa Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 21:10:04 +0200 Subject: [PATCH 041/189] Authentication Service: Remove "WithContext" API methods --- cloud/authentication.go | 32 ++++++-------------------------- cloud/authentication_test.go | 25 +++++++++++++------------ onpremise/authentication.go | 32 ++++++-------------------------- onpremise/authentication_test.go | 25 +++++++++++++------------ 4 files changed, 38 insertions(+), 76 deletions(-) diff --git a/cloud/authentication.go b/cloud/authentication.go index caa7c31..e0a4166 100644 --- a/cloud/authentication.go +++ b/cloud/authentication.go @@ -48,7 +48,7 @@ type Session struct { Cookies []*http.Cookie } -// AcquireSessionCookieWithContext creates a new session for a user in Jira. +// AcquireSessionCookie creates a new session for a user in Jira. // Once a session has been successfully created it can be used to access any of Jira's remote APIs and also the web UI by passing the appropriate HTTP Cookie header. // The header will by automatically applied to every API request. // Note that it is generally preferrable to use HTTP BASIC authentication with the REST API. @@ -57,7 +57,7 @@ type Session struct { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session // // Deprecated: Use CookieAuthTransport instead -func (s *AuthenticationService) AcquireSessionCookieWithContext(ctx context.Context, username, password string) (bool, error) { +func (s *AuthenticationService) AcquireSessionCookie(ctx context.Context, username, password string) (bool, error) { apiEndpoint := "rest/auth/1/session" body := struct { Username string `json:"username"` @@ -92,13 +92,6 @@ func (s *AuthenticationService) AcquireSessionCookieWithContext(ctx context.Cont return true, nil } -// AcquireSessionCookie wraps AcquireSessionCookieWithContext using the background context. -// -// Deprecated: Use CookieAuthTransport instead -func (s *AuthenticationService) AcquireSessionCookie(username, password string) (bool, error) { - return s.AcquireSessionCookieWithContext(context.Background(), username, password) -} - // SetBasicAuth sets username and password for the basic auth against the Jira instance. // // Deprecated: Use BasicAuthTransport instead @@ -121,13 +114,13 @@ func (s *AuthenticationService) Authenticated() bool { return false } -// LogoutWithContext logs out the current user that has been authenticated and the session in the client is destroyed. +// Logout logs out the current user that has been authenticated and the session in the client is destroyed. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session // // Deprecated: Use CookieAuthTransport to create base client. Logging out is as simple as not using the // client anymore -func (s *AuthenticationService) LogoutWithContext(ctx context.Context) error { +func (s *AuthenticationService) Logout(ctx context.Context) error { if s.authType != authTypeSession || s.client.session == nil { return fmt.Errorf("no user is authenticated") } @@ -154,18 +147,10 @@ func (s *AuthenticationService) LogoutWithContext(ctx context.Context) error { } -// Logout wraps LogoutWithContext using the background context. -// -// Deprecated: Use CookieAuthTransport to create base client. Logging out is as simple as not using the -// client anymore -func (s *AuthenticationService) Logout() error { - return s.LogoutWithContext(context.Background()) -} - -// GetCurrentUserWithContext gets the details of the current user. +// GetCurrentUser gets the details of the current user. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session -func (s *AuthenticationService) GetCurrentUserWithContext(ctx context.Context) (*Session, error) { +func (s *AuthenticationService) GetCurrentUser(ctx context.Context) (*Session, error) { if s == nil { return nil, fmt.Errorf("authentication Service is not instantiated") } @@ -201,8 +186,3 @@ func (s *AuthenticationService) GetCurrentUserWithContext(ctx context.Context) ( return ret, nil } - -// GetCurrentUser wraps GetCurrentUserWithContext using the background context. -func (s *AuthenticationService) GetCurrentUser() (*Session, error) { - return s.GetCurrentUserWithContext(context.Background()) -} diff --git a/cloud/authentication_test.go b/cloud/authentication_test.go index e94b5ed..b0b6d9d 100644 --- a/cloud/authentication_test.go +++ b/cloud/authentication_test.go @@ -2,6 +2,7 @@ package cloud import ( "bytes" + "context" "fmt" "io" "net/http" @@ -30,7 +31,7 @@ func TestAuthenticationService_AcquireSessionCookie_Failure(t *testing.T) { w.WriteHeader(http.StatusInternalServerError) }) - res, err := testClient.Authentication.AcquireSessionCookie("foo", "bar") + res, err := testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") if err == nil { t.Errorf("Expected error, but no error given") } @@ -63,7 +64,7 @@ func TestAuthenticationService_AcquireSessionCookie_Success(t *testing.T) { fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) }) - res, err := testClient.Authentication.AcquireSessionCookie("foo", "bar") + res, err := testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") if err != nil { t.Errorf("No error expected. Got %s", err) } @@ -162,9 +163,9 @@ func TestAuthenticationService_GetUserInfo_AccessForbidden_Fail(t *testing.T) { } }) - testClient.Authentication.AcquireSessionCookie("foo", "bar") + testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") - _, err := testClient.Authentication.GetCurrentUser() + _, err := testClient.Authentication.GetCurrentUser(context.Background()) if err == nil { t.Errorf("Non nil error expect, received nil") } @@ -200,9 +201,9 @@ func TestAuthenticationService_GetUserInfo_NonOkStatusCode_Fail(t *testing.T) { } }) - testClient.Authentication.AcquireSessionCookie("foo", "bar") + testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") - _, err := testClient.Authentication.GetCurrentUser() + _, err := testClient.Authentication.GetCurrentUser(context.Background()) if err == nil { t.Errorf("Non nil error expect, received nil") } @@ -212,7 +213,7 @@ func TestAuthenticationService_GetUserInfo_FailWithoutLogin(t *testing.T) { // no setup() required here testClient = new(Client) - _, err := testClient.Authentication.GetCurrentUser() + _, err := testClient.Authentication.GetCurrentUser(context.Background()) if err == nil { t.Errorf("Expected error, but got %s", err) } @@ -255,9 +256,9 @@ func TestAuthenticationService_GetUserInfo_Success(t *testing.T) { } }) - testClient.Authentication.AcquireSessionCookie("foo", "bar") + testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") - userinfo, err := testClient.Authentication.GetCurrentUser() + userinfo, err := testClient.Authentication.GetCurrentUser(context.Background()) if err != nil { t.Errorf("Nil error expect, received %s", err) } @@ -296,9 +297,9 @@ func TestAuthenticationService_Logout_Success(t *testing.T) { } }) - testClient.Authentication.AcquireSessionCookie("foo", "bar") + testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") - err := testClient.Authentication.Logout() + err := testClient.Authentication.Logout(context.Background()) if err != nil { t.Errorf("Expected nil error, got %s", err) } @@ -314,7 +315,7 @@ func TestAuthenticationService_Logout_FailWithoutLogin(t *testing.T) { w.WriteHeader(http.StatusUnauthorized) } }) - err := testClient.Authentication.Logout() + err := testClient.Authentication.Logout(context.Background()) if err == nil { t.Error("Expected not nil, got nil") } diff --git a/onpremise/authentication.go b/onpremise/authentication.go index baade93..62c8b50 100644 --- a/onpremise/authentication.go +++ b/onpremise/authentication.go @@ -48,7 +48,7 @@ type Session struct { Cookies []*http.Cookie } -// AcquireSessionCookieWithContext creates a new session for a user in Jira. +// AcquireSessionCookie creates a new session for a user in Jira. // Once a session has been successfully created it can be used to access any of Jira's remote APIs and also the web UI by passing the appropriate HTTP Cookie header. // The header will by automatically applied to every API request. // Note that it is generally preferrable to use HTTP BASIC authentication with the REST API. @@ -57,7 +57,7 @@ type Session struct { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session // // Deprecated: Use CookieAuthTransport instead -func (s *AuthenticationService) AcquireSessionCookieWithContext(ctx context.Context, username, password string) (bool, error) { +func (s *AuthenticationService) AcquireSessionCookie(ctx context.Context, username, password string) (bool, error) { apiEndpoint := "rest/auth/1/session" body := struct { Username string `json:"username"` @@ -92,13 +92,6 @@ func (s *AuthenticationService) AcquireSessionCookieWithContext(ctx context.Cont return true, nil } -// AcquireSessionCookie wraps AcquireSessionCookieWithContext using the background context. -// -// Deprecated: Use CookieAuthTransport instead -func (s *AuthenticationService) AcquireSessionCookie(username, password string) (bool, error) { - return s.AcquireSessionCookieWithContext(context.Background(), username, password) -} - // SetBasicAuth sets username and password for the basic auth against the Jira instance. // // Deprecated: Use BasicAuthTransport instead @@ -121,13 +114,13 @@ func (s *AuthenticationService) Authenticated() bool { return false } -// LogoutWithContext logs out the current user that has been authenticated and the session in the client is destroyed. +// Logout logs out the current user that has been authenticated and the session in the client is destroyed. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session // // Deprecated: Use CookieAuthTransport to create base client. Logging out is as simple as not using the // client anymore -func (s *AuthenticationService) LogoutWithContext(ctx context.Context) error { +func (s *AuthenticationService) Logout(ctx context.Context) error { if s.authType != authTypeSession || s.client.session == nil { return fmt.Errorf("no user is authenticated") } @@ -154,18 +147,10 @@ func (s *AuthenticationService) LogoutWithContext(ctx context.Context) error { } -// Logout wraps LogoutWithContext using the background context. -// -// Deprecated: Use CookieAuthTransport to create base client. Logging out is as simple as not using the -// client anymore -func (s *AuthenticationService) Logout() error { - return s.LogoutWithContext(context.Background()) -} - -// GetCurrentUserWithContext gets the details of the current user. +// GetCurrentUser gets the details of the current user. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session -func (s *AuthenticationService) GetCurrentUserWithContext(ctx context.Context) (*Session, error) { +func (s *AuthenticationService) GetCurrentUser(ctx context.Context) (*Session, error) { if s == nil { return nil, fmt.Errorf("authentication Service is not instantiated") } @@ -201,8 +186,3 @@ func (s *AuthenticationService) GetCurrentUserWithContext(ctx context.Context) ( return ret, nil } - -// GetCurrentUser wraps GetCurrentUserWithContext using the background context. -func (s *AuthenticationService) GetCurrentUser() (*Session, error) { - return s.GetCurrentUserWithContext(context.Background()) -} diff --git a/onpremise/authentication_test.go b/onpremise/authentication_test.go index c6c0ee7..7b602a4 100644 --- a/onpremise/authentication_test.go +++ b/onpremise/authentication_test.go @@ -2,6 +2,7 @@ package onpremise import ( "bytes" + "context" "fmt" "io" "net/http" @@ -30,7 +31,7 @@ func TestAuthenticationService_AcquireSessionCookie_Failure(t *testing.T) { w.WriteHeader(http.StatusInternalServerError) }) - res, err := testClient.Authentication.AcquireSessionCookie("foo", "bar") + res, err := testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") if err == nil { t.Errorf("Expected error, but no error given") } @@ -63,7 +64,7 @@ func TestAuthenticationService_AcquireSessionCookie_Success(t *testing.T) { fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) }) - res, err := testClient.Authentication.AcquireSessionCookie("foo", "bar") + res, err := testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") if err != nil { t.Errorf("No error expected. Got %s", err) } @@ -162,9 +163,9 @@ func TestAuthenticationService_GetUserInfo_AccessForbidden_Fail(t *testing.T) { } }) - testClient.Authentication.AcquireSessionCookie("foo", "bar") + testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") - _, err := testClient.Authentication.GetCurrentUser() + _, err := testClient.Authentication.GetCurrentUser(context.Background()) if err == nil { t.Errorf("Non nil error expect, received nil") } @@ -200,9 +201,9 @@ func TestAuthenticationService_GetUserInfo_NonOkStatusCode_Fail(t *testing.T) { } }) - testClient.Authentication.AcquireSessionCookie("foo", "bar") + testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") - _, err := testClient.Authentication.GetCurrentUser() + _, err := testClient.Authentication.GetCurrentUser(context.Background()) if err == nil { t.Errorf("Non nil error expect, received nil") } @@ -212,7 +213,7 @@ func TestAuthenticationService_GetUserInfo_FailWithoutLogin(t *testing.T) { // no setup() required here testClient = new(Client) - _, err := testClient.Authentication.GetCurrentUser() + _, err := testClient.Authentication.GetCurrentUser(context.Background()) if err == nil { t.Errorf("Expected error, but got %s", err) } @@ -255,9 +256,9 @@ func TestAuthenticationService_GetUserInfo_Success(t *testing.T) { } }) - testClient.Authentication.AcquireSessionCookie("foo", "bar") + testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") - userinfo, err := testClient.Authentication.GetCurrentUser() + userinfo, err := testClient.Authentication.GetCurrentUser(context.Background()) if err != nil { t.Errorf("Nil error expect, received %s", err) } @@ -296,9 +297,9 @@ func TestAuthenticationService_Logout_Success(t *testing.T) { } }) - testClient.Authentication.AcquireSessionCookie("foo", "bar") + testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") - err := testClient.Authentication.Logout() + err := testClient.Authentication.Logout(context.Background()) if err != nil { t.Errorf("Expected nil error, got %s", err) } @@ -314,7 +315,7 @@ func TestAuthenticationService_Logout_FailWithoutLogin(t *testing.T) { w.WriteHeader(http.StatusUnauthorized) } }) - err := testClient.Authentication.Logout() + err := testClient.Authentication.Logout(context.Background()) if err == nil { t.Error("Expected not nil, got nil") } From b564b598e4e37d45686c6946076fcc843e068c06 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 21:19:31 +0200 Subject: [PATCH 042/189] Replaced "GET" with http.MethodGet Related: #508 --- cloud/auth_transport_basic_test.go | 2 +- cloud/auth_transport_cookie_test.go | 6 ++-- cloud/authentication.go | 2 +- cloud/authentication_test.go | 12 +++---- cloud/board.go | 9 ++--- cloud/board_test.go | 14 ++++---- cloud/error_test.go | 8 ++--- cloud/examples/do/main.go | 3 +- cloud/field.go | 7 ++-- cloud/field_test.go | 2 +- cloud/filter.go | 11 +++--- cloud/filter_test.go | 10 +++--- cloud/group.go | 5 +-- cloud/group_test.go | 4 +-- cloud/issue.go | 16 ++++----- cloud/issue_test.go | 48 ++++++++++++------------- cloud/issuelinktype.go | 5 +-- cloud/issuelinktype_test.go | 4 +-- cloud/jira_test.go | 28 +++++++-------- cloud/metaissue.go | 5 +-- cloud/metaissue_test.go | 6 ++-- cloud/organization.go | 11 +++--- cloud/organization_test.go | 10 +++--- cloud/permissionscheme.go | 5 +-- cloud/permissionscheme_test.go | 8 ++--- cloud/priority.go | 7 ++-- cloud/priority_test.go | 2 +- cloud/project.go | 7 ++-- cloud/project_test.go | 12 +++---- cloud/resolution.go | 7 ++-- cloud/resolution_test.go | 2 +- cloud/role.go | 5 +-- cloud/role_test.go | 8 ++--- cloud/servicedesk.go | 5 +-- cloud/servicedesk_test.go | 6 ++-- cloud/sprint.go | 5 +-- cloud/sprint_test.go | 4 +-- cloud/status.go | 7 ++-- cloud/status_test.go | 2 +- cloud/statuscategory.go | 7 ++-- cloud/statuscategory_test.go | 2 +- cloud/user.go | 11 +++--- cloud/user_test.go | 12 +++---- cloud/version.go | 3 +- cloud/version_test.go | 2 +- onpremise/auth_transport_basic_test.go | 2 +- onpremise/auth_transport_cookie_test.go | 6 ++-- onpremise/authentication.go | 2 +- onpremise/authentication_test.go | 12 +++---- onpremise/board.go | 9 ++--- onpremise/board_test.go | 14 ++++---- onpremise/error_test.go | 8 ++--- onpremise/examples/do/main.go | 3 +- onpremise/field.go | 7 ++-- onpremise/field_test.go | 2 +- onpremise/filter.go | 11 +++--- onpremise/filter_test.go | 10 +++--- onpremise/group.go | 5 +-- onpremise/group_test.go | 4 +-- onpremise/issue.go | 16 ++++----- onpremise/issue_test.go | 48 ++++++++++++------------- onpremise/issuelinktype.go | 5 +-- onpremise/issuelinktype_test.go | 4 +-- onpremise/jira_test.go | 28 +++++++-------- onpremise/metaissue.go | 5 +-- onpremise/metaissue_test.go | 6 ++-- onpremise/organization.go | 11 +++--- onpremise/organization_test.go | 10 +++--- onpremise/permissionscheme.go | 5 +-- onpremise/permissionscheme_test.go | 8 ++--- onpremise/priority.go | 7 ++-- onpremise/priority_test.go | 2 +- onpremise/project.go | 7 ++-- onpremise/project_test.go | 12 +++---- onpremise/resolution.go | 7 ++-- onpremise/resolution_test.go | 2 +- onpremise/role.go | 5 +-- onpremise/role_test.go | 8 ++--- onpremise/servicedesk.go | 5 +-- onpremise/servicedesk_test.go | 6 ++-- onpremise/sprint.go | 5 +-- onpremise/sprint_test.go | 4 +-- onpremise/status.go | 7 ++-- onpremise/status_test.go | 2 +- onpremise/statuscategory.go | 7 ++-- onpremise/statuscategory_test.go | 2 +- onpremise/user.go | 11 +++--- onpremise/user_test.go | 12 +++---- onpremise/version.go | 3 +- onpremise/version_test.go | 2 +- 90 files changed, 386 insertions(+), 328 deletions(-) diff --git a/cloud/auth_transport_basic_test.go b/cloud/auth_transport_basic_test.go index c7db151..421705e 100644 --- a/cloud/auth_transport_basic_test.go +++ b/cloud/auth_transport_basic_test.go @@ -31,7 +31,7 @@ func TestBasicAuthTransport(t *testing.T) { } basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) - req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) + req, _ := basicAuthClient.NewRequest(context.Background(), http.MethodGet, ".", nil) basicAuthClient.Do(req, nil) } diff --git a/cloud/auth_transport_cookie_test.go b/cloud/auth_transport_cookie_test.go index 14dde2c..1ce4340 100644 --- a/cloud/auth_transport_cookie_test.go +++ b/cloud/auth_transport_cookie_test.go @@ -38,7 +38,7 @@ func TestCookieAuthTransport_SessionObject_Exists(t *testing.T) { } basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) - req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) + req, _ := basicAuthClient.NewRequest(context.Background(), http.MethodGet, ".", nil) basicAuthClient.Do(req, nil) } @@ -74,7 +74,7 @@ func TestCookieAuthTransport_SessionObject_ExistsWithEmptyCookie(t *testing.T) { } basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) - req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) + req, _ := basicAuthClient.NewRequest(context.Background(), http.MethodGet, ".", nil) basicAuthClient.Do(req, nil) } @@ -115,6 +115,6 @@ func TestCookieAuthTransport_SessionObject_DoesNotExist(t *testing.T) { } basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) - req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) + req, _ := basicAuthClient.NewRequest(context.Background(), http.MethodGet, ".", nil) basicAuthClient.Do(req, nil) } diff --git a/cloud/authentication.go b/cloud/authentication.go index e0a4166..9ec36fa 100644 --- a/cloud/authentication.go +++ b/cloud/authentication.go @@ -159,7 +159,7 @@ func (s *AuthenticationService) GetCurrentUser(ctx context.Context) (*Session, e } apiEndpoint := "rest/auth/1/session" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, fmt.Errorf("could not create request for getting user info : %s", err) } diff --git a/cloud/authentication_test.go b/cloud/authentication_test.go index b0b6d9d..0f748d4 100644 --- a/cloud/authentication_test.go +++ b/cloud/authentication_test.go @@ -155,8 +155,8 @@ func TestAuthenticationService_GetUserInfo_AccessForbidden_Fail(t *testing.T) { fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) } - if r.Method == "GET" { - testMethod(t, r, "GET") + if r.Method == http.MethodGet { + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/auth/1/session") w.WriteHeader(http.StatusForbidden) @@ -193,8 +193,8 @@ func TestAuthenticationService_GetUserInfo_NonOkStatusCode_Fail(t *testing.T) { fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) } - if r.Method == "GET" { - testMethod(t, r, "GET") + if r.Method == http.MethodGet { + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/auth/1/session") //any status but 200 w.WriteHeader(240) @@ -249,8 +249,8 @@ func TestAuthenticationService_GetUserInfo_Success(t *testing.T) { fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) } - if r.Method == "GET" { - testMethod(t, r, "GET") + if r.Method == http.MethodGet { + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/auth/1/session") fmt.Fprint(w, `{"self":"https://my.jira.com/rest/api/latest/user?username=foo","name":"foo","loginInfo":{"failedLoginCount":12,"loginCount":357,"lastFailedLoginTime":"2016-09-06T16:41:23.949+0200","previousLoginTime":"2016-09-07T11:36:23.476+0200"}}`) } diff --git a/cloud/board.go b/cloud/board.go index c1e9504..1d69dbd 100644 --- a/cloud/board.go +++ b/cloud/board.go @@ -3,6 +3,7 @@ package cloud import ( "context" "fmt" + "net/http" "strconv" "time" ) @@ -134,7 +135,7 @@ func (s *BoardService) GetAllBoards(ctx context.Context, opt *BoardListOptions) if err != nil { return nil, nil, err } - req, err := s.client.NewRequest(ctx, "GET", url, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, url, nil) if err != nil { return nil, nil, err } @@ -155,7 +156,7 @@ func (s *BoardService) GetAllBoards(ctx context.Context, opt *BoardListOptions) // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard func (s *BoardService) GetBoard(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -241,7 +242,7 @@ func (s *BoardService) GetAllSprintsWithOptions(ctx context.Context, boardID int if err != nil { return nil, nil, err } - req, err := s.client.NewRequest(ctx, "GET", url, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, url, nil) if err != nil { return nil, nil, err } @@ -260,7 +261,7 @@ func (s *BoardService) GetAllSprintsWithOptions(ctx context.Context, boardID int func (s *BoardService) GetBoardConfiguration(ctx context.Context, boardID int) (*BoardConfiguration, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/configuration", boardID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err diff --git a/cloud/board_test.go b/cloud/board_test.go index b2861f2..a3a05e1 100644 --- a/cloud/board_test.go +++ b/cloud/board_test.go @@ -18,7 +18,7 @@ func TestBoardService_GetAllBoards(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) @@ -43,7 +43,7 @@ func TestBoardService_GetAllBoards_WithFilter(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) testRequestParams(t, r, map[string]string{"type": "scrum", "name": "Test", "startAt": "1", "maxResults": "10", "projectKeyOrId": "TE"}) fmt.Fprint(w, string(raw)) @@ -72,7 +72,7 @@ func TestBoardService_GetBoard(t *testing.T) { testAPIEdpoint := "/rest/agile/1.0/board/1" testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, `{"id":4,"self":"https://test.jira.org/rest/agile/1.0/board/1","name":"Test Weekly","type":"scrum"}`) }) @@ -92,7 +92,7 @@ func TestBoardService_GetBoard_WrongID(t *testing.T) { testAPIEndpoint := "/rest/api/2/board/99999999" testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, nil) }) @@ -167,7 +167,7 @@ func TestBoardService_GetAllSprints(t *testing.T) { } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -199,7 +199,7 @@ func TestBoardService_GetAllSprintsWithOptions(t *testing.T) { } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -230,7 +230,7 @@ func TestBoardService_GetBoardConfigoration(t *testing.T) { } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) diff --git a/cloud/error_test.go b/cloud/error_test.go index b8e55cd..dcc51ee 100644 --- a/cloud/error_test.go +++ b/cloud/error_test.go @@ -18,7 +18,7 @@ func TestError_NewJiraError(t *testing.T) { fmt.Fprint(w, `{"errorMessages":["Issue does not exist or you do not have permission to see it."],"errors":{}}`) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, errors.New("Original http error")) @@ -52,7 +52,7 @@ func TestError_NoJSON(t *testing.T) { fmt.Fprint(w, `Original message body`) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, errors.New("Original http error")) @@ -72,7 +72,7 @@ func TestError_Unauthorized_NilError(t *testing.T) { fmt.Fprint(w, `User is not authorized`) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, nil) @@ -91,7 +91,7 @@ func TestError_BadJSON(t *testing.T) { fmt.Fprint(w, `Not JSON`) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, errors.New("Original http error")) diff --git a/cloud/examples/do/main.go b/cloud/examples/do/main.go index eee2747..a69b435 100644 --- a/cloud/examples/do/main.go +++ b/cloud/examples/do/main.go @@ -3,13 +3,14 @@ package main import ( "context" "fmt" + "net/http" jira "github.com/andygrunwald/go-jira/cloud" ) func main() { jiraClient, _ := jira.NewClient("https://jira.atlassian.com/", nil) - req, _ := jiraClient.NewRequest(context.Background(), "GET", "/rest/api/2/project", nil) + req, _ := jiraClient.NewRequest(context.Background(), http.MethodGet, "/rest/api/2/project", nil) projects := new([]jira.Project) res, err := jiraClient.Do(req, projects) diff --git a/cloud/field.go b/cloud/field.go index 5fbbc0f..93cb030 100644 --- a/cloud/field.go +++ b/cloud/field.go @@ -1,6 +1,9 @@ package cloud -import "context" +import ( + "context" + "net/http" +) // FieldService handles fields for the Jira instance / API. // @@ -34,7 +37,7 @@ type FieldSchema struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get func (s *FieldService) GetList(ctx context.Context) ([]Field, *Response, error) { apiEndpoint := "rest/api/2/field" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/field_test.go b/cloud/field_test.go index 93aade9..c6f9fe8 100644 --- a/cloud/field_test.go +++ b/cloud/field_test.go @@ -18,7 +18,7 @@ func TestFieldService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) diff --git a/cloud/filter.go b/cloud/filter.go index 52f942b..65035b2 100644 --- a/cloud/filter.go +++ b/cloud/filter.go @@ -3,6 +3,7 @@ package cloud import ( "context" "fmt" + "net/http" "github.com/google/go-querystring/query" ) @@ -123,7 +124,7 @@ func (fs *FilterService) GetList(ctx context.Context) ([]*Filter, *Response, err options := &GetQueryOptions{} apiEndpoint := "rest/api/2/filter" - req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := fs.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -146,7 +147,7 @@ func (fs *FilterService) GetList(ctx context.Context) ([]*Filter, *Response, err // GetFavouriteList retrieves the user's favourited filters from Jira func (fs *FilterService) GetFavouriteList(ctx context.Context) ([]*Filter, *Response, error) { apiEndpoint := "rest/api/2/filter/favourite" - req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := fs.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -162,7 +163,7 @@ func (fs *FilterService) GetFavouriteList(ctx context.Context) ([]*Filter, *Resp // Get retrieves a single Filter from Jira func (fs *FilterService) Get(ctx context.Context, filterID int) (*Filter, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/filter/%d", filterID) - req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := fs.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -185,7 +186,7 @@ func (fs *FilterService) GetMyFilters(ctx context.Context, opts *GetMyFiltersQue if err != nil { return nil, nil, err } - req, err := fs.client.NewRequest(ctx, "GET", url, nil) + req, err := fs.client.NewRequest(ctx, http.MethodGet, url, nil) if err != nil { return nil, nil, err } @@ -208,7 +209,7 @@ func (fs *FilterService) Search(ctx context.Context, opt *FilterSearchOptions) ( if err != nil { return nil, nil, err } - req, err := fs.client.NewRequest(ctx, "GET", url, nil) + req, err := fs.client.NewRequest(ctx, http.MethodGet, url, nil) if err != nil { return nil, nil, err } diff --git a/cloud/filter_test.go b/cloud/filter_test.go index e84fda0..e4fa5f3 100644 --- a/cloud/filter_test.go +++ b/cloud/filter_test.go @@ -17,7 +17,7 @@ func TestFilterService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEndpoint) fmt.Fprint(writer, string(raw)) }) @@ -40,7 +40,7 @@ func TestFilterService_Get(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEndpoint) fmt.Fprint(writer, string(raw)) }) @@ -64,7 +64,7 @@ func TestFilterService_GetFavouriteList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEndpoint) fmt.Fprint(writer, string(raw)) }) @@ -87,7 +87,7 @@ func TestFilterService_GetMyFilters(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEndpoint) fmt.Fprint(writer, string(raw)) }) @@ -111,7 +111,7 @@ func TestFilterService_Search(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEndpoint) fmt.Fprint(writer, string(raw)) }) diff --git a/cloud/group.go b/cloud/group.go index 1b30c3a..bfd2a5c 100644 --- a/cloud/group.go +++ b/cloud/group.go @@ -3,6 +3,7 @@ package cloud import ( "context" "fmt" + "net/http" "net/url" ) @@ -66,7 +67,7 @@ type GroupSearchOptions struct { // WARNING: This API only returns the first page of group members func (s *GroupService) Get(ctx context.Context, name string) ([]GroupMember, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -98,7 +99,7 @@ func (s *GroupService) GetWithOptions(ctx context.Context, name string, options options.IncludeInactiveUsers, ) } - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/group_test.go b/cloud/group_test.go index f95ad44..cc9fdfe 100644 --- a/cloud/group_test.go +++ b/cloud/group_test.go @@ -11,7 +11,7 @@ func TestGroupService_Get(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/group/member", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/group/member?groupname=default") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/group/member?includeInactiveUsers=false&maxResults=50&groupname=default&startAt=0","maxResults":50,"startAt":0,"total":2,"isLast":true,"values":[{"self":"http://www.example.com/jira/rest/api/2/user?username=michael","name":"michael","key":"michael","emailAddress":"michael@example.com","displayName":"MichaelScofield","active":true,"timeZone":"Australia/Sydney"},{"self":"http://www.example.com/jira/rest/api/2/user?username=alex","name":"alex","key":"alex","emailAddress":"alex@example.com","displayName":"AlexanderMahone","active":true,"timeZone":"Australia/Sydney"}]}`) }) @@ -26,7 +26,7 @@ func TestGroupService_GetPage(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/group/member", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/group/member?groupname=default") startAt := r.URL.Query().Get("startAt") if startAt == "0" { diff --git a/cloud/issue.go b/cloud/issue.go index a0b89c1..f500e2a 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -615,7 +615,7 @@ type RemoteLinkStatus struct { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getIssue func (s *IssueService) Get(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -644,7 +644,7 @@ func (s *IssueService) Get(ctx context.Context, issueID string, options *GetQuer // Caller must close resp.Body. func (s *IssueService) DownloadAttachment(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("secure/attachment/%s/", attachmentID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, err } @@ -741,7 +741,7 @@ func (s *IssueService) DeleteLink(ctx context.Context, linkID string) (*Response func (s *IssueService) GetWorklogs(ctx context.Context, issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -1027,7 +1027,7 @@ func (s *IssueService) Search(ctx context.Context, jql string, options *SearchOp u.RawQuery = uv.Encode() - req, err := s.client.NewRequest(ctx, "GET", u.String(), nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, u.String(), nil) if err != nil { return []Issue{}, nil, err } @@ -1087,7 +1087,7 @@ func (s *IssueService) SearchPages(ctx context.Context, jql string, options *Sea // GetCustomFields returns a map of customfield_* keys with string values func (s *IssueService) GetCustomFields(ctx context.Context, issueID string) (CustomFields, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -1127,7 +1127,7 @@ func (s *IssueService) GetCustomFields(ctx context.Context, issueID string) (Cus // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getTransitions func (s *IssueService) GetTransitions(ctx context.Context, id string) ([]Transition, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions?expand=transitions.fields", id) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -1280,7 +1280,7 @@ func (s *IssueService) Delete(ctx context.Context, issueID string) (*Response, e func (s *IssueService) GetWatchers(ctx context.Context, issueID string) (*[]User, *Response, error) { watchesAPIEndpoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) - req, err := s.client.NewRequest(ctx, "GET", watchesAPIEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, watchesAPIEndpoint, nil) if err != nil { return nil, nil, err } @@ -1381,7 +1381,7 @@ func (c ChangelogHistory) CreatedTime() (time.Time, error) { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getRemoteIssueLinks func (s *IssueService) GetRemoteLinks(ctx context.Context, id string) (*[]RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", id) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/issue_test.go b/cloud/issue_test.go index ff0e5a8..476b408 100644 --- a/cloud/issue_test.go +++ b/cloud/issue_test.go @@ -20,7 +20,7 @@ func TestIssueService_Get_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) @@ -39,7 +39,7 @@ func TestIssueService_Get_WithQuerySuccess(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002?expand=foo") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) @@ -108,7 +108,7 @@ func TestIssueService_CreateThenGet(t *testing.T) { } testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") bytes, err := json.Marshal(issue) @@ -339,7 +339,7 @@ func TestIssueService_Get_Fields(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) @@ -369,7 +369,7 @@ func TestIssueService_Get_RenderedFields(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{},"renderedFields":{"resolutiondate":"In 1 week","updated":"2 hours ago","comment":{"comments":[{"body":"This is HTML"}]}}}`) @@ -402,7 +402,7 @@ func TestIssueService_DownloadAttachment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/secure/attachment/", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/secure/attachment/10000/") w.WriteHeader(http.StatusOK) @@ -437,7 +437,7 @@ func TestIssueService_DownloadAttachment_BadStatus(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/secure/attachment/", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/secure/attachment/10000/") w.WriteHeader(http.StatusForbidden) @@ -621,7 +621,7 @@ func TestIssueService_Search(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/search?expand=foo&jql=type+%3D+Bug+and+Status+NOT+IN+%28Resolved%29&maxResults=40&startAt=1") w.WriteHeader(http.StatusOK) fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 40,"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}}}]}`) @@ -652,7 +652,7 @@ func TestIssueService_SearchEmptyJQL(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/search?expand=foo&maxResults=40&startAt=1") w.WriteHeader(http.StatusOK) fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 40,"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}}}]}`) @@ -683,7 +683,7 @@ func TestIssueService_Search_WithoutPaging(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/search?jql=something") 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}}}]}`) @@ -712,7 +712,7 @@ func TestIssueService_SearchPages(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) if r.URL.String() == "/rest/api/2/search?expand=foo&jql=something&maxResults=2&startAt=1&validateQuery=warn" { w.WriteHeader(http.StatusOK) fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 2,"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}}}]}`) @@ -750,7 +750,7 @@ func TestIssueService_SearchPages_EmptyResult(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) if r.URL.String() == "/rest/api/2/search?expand=foo&jql=something&maxResults=50&startAt=1&validateQuery=warn" { w.WriteHeader(http.StatusOK) // This is what Jira outputs when the &maxResult= issue occurs. It used to cause SearchPages to go into an endless loop. @@ -778,7 +778,7 @@ func TestIssueService_GetCustomFields(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"customfield_123":"test","watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) @@ -800,7 +800,7 @@ func TestIssueService_GetComplexCustomFields(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"customfield_123":{"self":"http://www.example.com/jira/rest/api/2/customFieldOption/123","value":"test","id":"123"},"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) @@ -830,7 +830,7 @@ func TestIssueService_GetTransitions(t *testing.T) { } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -1559,7 +1559,7 @@ func TestIssueService_GetWorklogs(t *testing.T) { t.Run(tc.name, func(t *testing.T) { uri := fmt.Sprintf(tc.uri, tc.issueId) testMux.HandleFunc(uri, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, uri) _, _ = fmt.Fprint(w, tc.response) }) @@ -1588,14 +1588,14 @@ func TestIssueService_GetWatchers(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002/watchers", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002/watchers") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","accountId": "000000000000000000000000","displayName":"Fred F. User","active":false}]}`) }) testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","key":"fred","accountId": "000000000000000000000000", @@ -1629,14 +1629,14 @@ func TestIssueService_DeprecatedGetWatchers(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002/watchers", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002/watchers") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000", "accountId": "000000000000000000000000", "displayName":"Fred F. User","active":false}]}`) }) testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000", "accountId": "000000000000000000000000", "key": "", "name": "", "emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", @@ -1691,7 +1691,7 @@ func TestIssueService_Get_Fields_Changelog(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"changelog","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","changelog":{"startAt": 0,"maxResults": 1, "total": 1, "histories": [{"id": "10002", "author": {"self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "key": "fred", "emailAddress": "fred@example.com", "avatarUrls": {"48x48": "http://www.example.com/secure/useravatar?ownerId=fred&avatarId=33072", "24x24": "http://www.example.com/secure/useravatar?size=small&ownerId=fred&avatarId=33072", "16x16": "http://www.example.com/secure/useravatar?size=xsmall&ownerId=fred&avatarId=33072", "32x32": "http://www.example.com/secure/useravatar?size=medium&ownerId=fred&avatarId=33072"},"displayName":"Fred","active": true,"timeZone":"Australia/Sydney"},"created":"2018-06-20T16:50:35.000+0300","items":[{"field":"Rank","fieldtype":"custom","from":"","fromString":"","to":"","toString":"Ranked higher"}]}]}}`) @@ -1722,7 +1722,7 @@ func TestIssueService_Get_Transitions(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/api/latest/issue/10002","key":"EX-1","transitions":[{"id":"121","name":"Start","to":{"self":"http://www.example.com/rest/api/2/status/10444","description":"","iconUrl":"http://www.example.com/images/icons/statuses/inprogress.png","name":"In progress","id":"10444","statusCategory":{"self":"http://www.example.com/rest/api/2/statuscategory/4","id":4,"key":"indeterminate","colorName":"yellow","name":"In Progress"}}}]}`) @@ -1753,7 +1753,7 @@ func TestIssueService_Get_Fields_AffectsVersions(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"fields":{"versions":[{"self":"http://www.example.com/jira/rest/api/2/version/10705","id":"10705","description":"test description","name":"2.1.0-rc3","archived":false,"released":false,"releaseDate":"2018-09-30"}]}}`) @@ -1794,7 +1794,7 @@ func TestIssueService_GetRemoteLinks(t *testing.T) { } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) diff --git a/cloud/issuelinktype.go b/cloud/issuelinktype.go index 0ab8b03..bee53e1 100644 --- a/cloud/issuelinktype.go +++ b/cloud/issuelinktype.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "net/http" ) // IssueLinkTypeService handles issue link types for the Jira instance / API. @@ -17,7 +18,7 @@ type IssueLinkTypeService service // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-get func (s *IssueLinkTypeService) GetList(ctx context.Context) ([]IssueLinkType, *Response, error) { apiEndpoint := "rest/api/2/issueLinkType" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -35,7 +36,7 @@ func (s *IssueLinkTypeService) GetList(ctx context.Context) ([]IssueLinkType, *R // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-get func (s *IssueLinkTypeService) Get(ctx context.Context, ID string) (*IssueLinkType, *Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) - req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/issuelinktype_test.go b/cloud/issuelinktype_test.go index 23df4f4..c9a3e59 100644 --- a/cloud/issuelinktype_test.go +++ b/cloud/issuelinktype_test.go @@ -18,7 +18,7 @@ func TestIssueLinkTypeService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -36,7 +36,7 @@ func TestIssueLinkTypeService_Get(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issueLinkType/123", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issueLinkType/123") fmt.Fprint(w, `{"id": "123","name": "Blocked","inward": "Blocked","outward": "Blocked", diff --git a/cloud/jira_test.go b/cloud/jira_test.go index 7bf134e..9f4869c 100644 --- a/cloud/jira_test.go +++ b/cloud/jira_test.go @@ -165,7 +165,7 @@ func TestClient_NewRequest(t *testing.T) { inURL, outURL := "rest/api/2/issue/", testJiraInstanceURL+"rest/api/2/issue/" inBody, outBody := &Issue{Key: "MESOS"}, `{"key":"MESOS"}`+"\n" - req, _ := c.NewRequest(context.Background(), "GET", inURL, inBody) + req, _ := c.NewRequest(context.Background(), http.MethodGet, inURL, inBody) // Test that relative URL was expanded if got, want := req.URL.String(), outURL; got != want { @@ -189,7 +189,7 @@ func TestClient_NewRawRequest(t *testing.T) { outBody := `{"key":"MESOS"}` + "\n" inBody := outBody - req, _ := c.NewRawRequest(context.Background(), "GET", inURL, strings.NewReader(outBody)) + req, _ := c.NewRawRequest(context.Background(), http.MethodGet, inURL, strings.NewReader(outBody)) // Test that relative URL was expanded if got, want := req.URL.String(), outURL; got != want { @@ -217,7 +217,7 @@ func TestClient_NewRequest_BadURL(t *testing.T) { if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } - _, err = c.NewRequest(context.Background(), "GET", ":", nil) + _, err = c.NewRequest(context.Background(), http.MethodGet, ":", nil) testURLParseError(t, err) } @@ -233,7 +233,7 @@ func TestClient_NewRequest_SessionCookies(t *testing.T) { inURL := "rest/api/2/issue/" inBody := &Issue{Key: "MESOS"} - req, err := c.NewRequest(context.Background(), "GET", inURL, inBody) + req, err := c.NewRequest(context.Background(), http.MethodGet, inURL, inBody) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) @@ -260,7 +260,7 @@ func TestClient_NewRequest_BasicAuth(t *testing.T) { inURL := "rest/api/2/issue/" inBody := &Issue{Key: "MESOS"} - req, err := c.NewRequest(context.Background(), "GET", inURL, inBody) + req, err := c.NewRequest(context.Background(), http.MethodGet, inURL, inBody) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) @@ -281,7 +281,7 @@ func TestClient_NewRequest_EmptyBody(t *testing.T) { if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } - req, err := c.NewRequest(context.Background(), "GET", "/", nil) + req, err := c.NewRequest(context.Background(), http.MethodGet, "/", nil) if err != nil { t.Fatalf("NewRequest returned unexpected error: %v", err) } @@ -302,7 +302,7 @@ func TestClient_NewMultiPartRequest(t *testing.T) { inURL := "rest/api/2/issue/" inBuf := bytes.NewBufferString("teststring") - req, err := c.NewMultiPartRequest(context.Background(), "GET", inURL, inBuf) + req, err := c.NewMultiPartRequest(context.Background(), http.MethodGet, inURL, inBuf) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) @@ -333,7 +333,7 @@ func TestClient_NewMultiPartRequest_BasicAuth(t *testing.T) { inURL := "rest/api/2/issue/" inBuf := bytes.NewBufferString("teststring") - req, err := c.NewMultiPartRequest(context.Background(), "GET", inURL, inBuf) + req, err := c.NewMultiPartRequest(context.Background(), http.MethodGet, inURL, inBuf) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) @@ -358,13 +358,13 @@ func TestClient_Do(t *testing.T) { } testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if m := "GET"; m != r.Method { + if m := http.MethodGet; m != r.Method { t.Errorf("Request method = %v, want %v", r.Method, m) } fmt.Fprint(w, `{"A":"a"}`) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) body := new(foo) testClient.Do(req, body) @@ -379,13 +379,13 @@ func TestClient_Do_HTTPResponse(t *testing.T) { defer teardown() testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if m := "GET"; m != r.Method { + if m := http.MethodGet; m != r.Method { t.Errorf("Request method = %v, want %v", r.Method, m) } fmt.Fprint(w, `{"A":"a"}`) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) res, _ := testClient.Do(req, nil) _, err := io.ReadAll(res.Body) @@ -404,7 +404,7 @@ func TestClient_Do_HTTPError(t *testing.T) { http.Error(w, "Bad Request", 400) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) _, err := testClient.Do(req, nil) if err == nil { @@ -422,7 +422,7 @@ func TestClient_Do_RedirectLoop(t *testing.T) { http.Redirect(w, r, "/", http.StatusFound) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) _, err := testClient.Do(req, nil) if err == nil { diff --git a/cloud/metaissue.go b/cloud/metaissue.go index dde3c83..1ae9bc9 100644 --- a/cloud/metaissue.go +++ b/cloud/metaissue.go @@ -3,6 +3,7 @@ package cloud import ( "context" "fmt" + "net/http" "strings" "github.com/google/go-querystring/query" @@ -57,7 +58,7 @@ func (s *IssueService) GetCreateMeta(ctx context.Context, projectkeys string) (* func (s *IssueService) GetCreateMetaWithOptions(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { apiEndpoint := "rest/api/2/issue/createmeta" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -83,7 +84,7 @@ func (s *IssueService) GetCreateMetaWithOptions(ctx context.Context, options *Ge func (s *IssueService) GetEditMeta(ctx context.Context, issue *Issue) (*EditMetaInfo, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/issue/%s/editmeta", issue.Key) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/metaissue_test.go b/cloud/metaissue_test.go index ef720e9..7292c67 100644 --- a/cloud/metaissue_test.go +++ b/cloud/metaissue_test.go @@ -15,7 +15,7 @@ func TestIssueService_GetCreateMeta_Success(t *testing.T) { testAPIEndpoint := "/rest/api/2/issue/createmeta" testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, `{ @@ -387,7 +387,7 @@ func TestIssueService_GetEditMeta_Success(t *testing.T) { testAPIEndpoint := "/rest/api/2/issue/PROJ-9001/editmeta" testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, `{ @@ -464,7 +464,7 @@ func TestMetaIssueType_GetCreateMetaWithOptions(t *testing.T) { testAPIEndpoint := "/rest/api/2/issue/createmeta" testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, `{ diff --git a/cloud/organization.go b/cloud/organization.go index a40b9a6..2f18316 100644 --- a/cloud/organization.go +++ b/cloud/organization.go @@ -3,6 +3,7 @@ package cloud import ( "context" "fmt" + "net/http" ) // OrganizationService handles Organizations for the Jira instance / API. @@ -66,7 +67,7 @@ func (s *OrganizationService) GetAllOrganizations(ctx context.Context, start int apiEndPoint += fmt.Sprintf("&accountId=%s", accountID) } - req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -121,7 +122,7 @@ func (s *OrganizationService) CreateOrganization(ctx context.Context, name strin func (s *OrganizationService) GetOrganization(ctx context.Context, organizationID int) (*Organization, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) - req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -172,7 +173,7 @@ func (s *OrganizationService) DeleteOrganization(ctx context.Context, organizati func (s *OrganizationService) GetPropertiesKeys(ctx context.Context, organizationID int) (*PropertyKeys, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property", organizationID) - req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -197,7 +198,7 @@ func (s *OrganizationService) GetPropertiesKeys(ctx context.Context, organizatio func (s *OrganizationService) GetProperty(ctx context.Context, organizationID int, propertyKey string) (*EntityProperty, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) - req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -272,7 +273,7 @@ func (s *OrganizationService) DeleteProperty(ctx context.Context, organizationID func (s *OrganizationService) GetUsers(ctx context.Context, organizationID int, start int, limit int) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user?start=%d&limit=%d", organizationID, start, limit) - req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { diff --git a/cloud/organization_test.go b/cloud/organization_test.go index 98581df..01feba6 100644 --- a/cloud/organization_test.go +++ b/cloud/organization_test.go @@ -12,7 +12,7 @@ func TestOrganizationService_GetAllOrganizations(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/servicedeskapi/organization") w.WriteHeader(http.StatusOK) @@ -63,7 +63,7 @@ func TestOrganizationService_GetOrganization(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/servicedeskapi/organization/1") w.WriteHeader(http.StatusOK) @@ -105,7 +105,7 @@ func TestOrganizationService_GetPropertiesKeys(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1/property", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/servicedeskapi/organization/1/property") w.WriteHeader(http.StatusOK) @@ -136,7 +136,7 @@ func TestOrganizationService_GetProperty(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1/property/organization.attributes", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/servicedeskapi/organization/1/property/organization.attributes") w.WriteHeader(http.StatusOK) @@ -203,7 +203,7 @@ func TestOrganizationService_GetUsers(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/servicedeskapi/organization/1/user") w.WriteHeader(http.StatusOK) diff --git a/cloud/permissionscheme.go b/cloud/permissionscheme.go index e3afa1d..51a1ee1 100644 --- a/cloud/permissionscheme.go +++ b/cloud/permissionscheme.go @@ -3,6 +3,7 @@ package cloud import ( "context" "fmt" + "net/http" ) // PermissionSchemeService handles permissionschemes for the Jira instance / API. @@ -32,7 +33,7 @@ type Holder struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-get func (s *PermissionSchemeService) GetList(ctx context.Context) (*PermissionSchemes, *Response, error) { apiEndpoint := "/rest/api/3/permissionscheme" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -52,7 +53,7 @@ func (s *PermissionSchemeService) GetList(ctx context.Context) (*PermissionSchem // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-schemeId-get func (s *PermissionSchemeService) Get(ctx context.Context, schemeID int) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/3/permissionscheme/%d", schemeID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/permissionscheme_test.go b/cloud/permissionscheme_test.go index 299c57a..4503289 100644 --- a/cloud/permissionscheme_test.go +++ b/cloud/permissionscheme_test.go @@ -18,7 +18,7 @@ func TestPermissionSchemeService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -46,7 +46,7 @@ func TestPermissionSchemeService_GetList_NoList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -69,7 +69,7 @@ func TestPermissionSchemeService_Get(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEdpoint) fmt.Fprint(writer, string(raw)) }) @@ -92,7 +92,7 @@ func TestPermissionSchemeService_Get_NoScheme(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEdpoint) fmt.Fprint(writer, string(raw)) }) diff --git a/cloud/priority.go b/cloud/priority.go index a431d74..b89e17f 100644 --- a/cloud/priority.go +++ b/cloud/priority.go @@ -1,6 +1,9 @@ package cloud -import "context" +import ( + "context" + "net/http" +) // PriorityService handles priorities for the Jira instance / API. // @@ -23,7 +26,7 @@ type Priority struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-priority-get func (s *PriorityService) GetList(ctx context.Context) ([]Priority, *Response, error) { apiEndpoint := "rest/api/2/priority" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/priority_test.go b/cloud/priority_test.go index 5994888..6fa6f23 100644 --- a/cloud/priority_test.go +++ b/cloud/priority_test.go @@ -18,7 +18,7 @@ func TestPriorityService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) diff --git a/cloud/project.go b/cloud/project.go index 2e55420..2480ece 100644 --- a/cloud/project.go +++ b/cloud/project.go @@ -3,6 +3,7 @@ package cloud import ( "context" "fmt" + "net/http" "github.com/google/go-querystring/query" ) @@ -92,7 +93,7 @@ func (s *ProjectService) GetList(ctx context.Context) (*ProjectList, *Response, // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects func (s *ProjectService) ListWithOptions(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { apiEndpoint := "rest/api/2/project" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -122,7 +123,7 @@ func (s *ProjectService) ListWithOptions(ctx context.Context, options *GetQueryO // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject func (s *ProjectService) Get(ctx context.Context, projectID string) (*Project, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/project/%s", projectID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -144,7 +145,7 @@ func (s *ProjectService) Get(ctx context.Context, projectID string) (*Project, * // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject func (s *ProjectService) GetPermissionScheme(ctx context.Context, projectID string) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s/permissionscheme", projectID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/project_test.go b/cloud/project_test.go index 8d37ff2..f21cceb 100644 --- a/cloud/project_test.go +++ b/cloud/project_test.go @@ -18,7 +18,7 @@ func TestProjectService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) @@ -42,7 +42,7 @@ func TestProjectService_ListWithOptions(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/project?expand=issueTypes") fmt.Fprint(w, string(raw)) }) @@ -66,7 +66,7 @@ func TestProjectService_Get(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) @@ -90,7 +90,7 @@ func TestProjectService_Get_NoProject(t *testing.T) { testAPIEdpoint := "/rest/api/2/project/99999999" testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, nil) }) @@ -114,7 +114,7 @@ func TestProjectService_GetPermissionScheme_Failure(t *testing.T) { testAPIEdpoint := "/rest/api/2/project/99999999/permissionscheme" testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, nil) }) @@ -138,7 +138,7 @@ func TestProjectService_GetPermissionScheme_Success(t *testing.T) { testAPIEdpoint := "/rest/api/2/project/99999999/permissionscheme" testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, `{ "expand": "permissions,user,group,projectRole,field,all", diff --git a/cloud/resolution.go b/cloud/resolution.go index e08c46d..d08fb59 100644 --- a/cloud/resolution.go +++ b/cloud/resolution.go @@ -1,6 +1,9 @@ package cloud -import "context" +import ( + "context" + "net/http" +) // ResolutionService handles resolutions for the Jira instance / API. // @@ -21,7 +24,7 @@ type Resolution struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-resolution-get func (s *ResolutionService) GetList(ctx context.Context) ([]Resolution, *Response, error) { apiEndpoint := "rest/api/2/resolution" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/resolution_test.go b/cloud/resolution_test.go index adcad66..d003e31 100644 --- a/cloud/resolution_test.go +++ b/cloud/resolution_test.go @@ -18,7 +18,7 @@ func TestResolutionService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) diff --git a/cloud/role.go b/cloud/role.go index ed918eb..4ab9327 100644 --- a/cloud/role.go +++ b/cloud/role.go @@ -3,6 +3,7 @@ package cloud import ( "context" "fmt" + "net/http" ) // RoleService handles roles for the Jira instance / API. @@ -39,7 +40,7 @@ type ActorUser struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-get func (s *RoleService) GetList(ctx context.Context) (*[]Role, *Response, error) { apiEndpoint := "rest/api/3/role" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -57,7 +58,7 @@ func (s *RoleService) GetList(ctx context.Context) (*[]Role, *Response, error) { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-id-get func (s *RoleService) Get(ctx context.Context, roleID int) (*Role, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/3/role/%d", roleID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/role_test.go b/cloud/role_test.go index a4c7a91..506f1a7 100644 --- a/cloud/role_test.go +++ b/cloud/role_test.go @@ -19,7 +19,7 @@ func TestRoleService_GetList_NoList(t *testing.T) { } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -43,7 +43,7 @@ func TestRoleService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -70,7 +70,7 @@ func TestRoleService_Get_NoRole(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEdpoint) fmt.Fprint(writer, string(raw)) }) @@ -93,7 +93,7 @@ func TestRoleService_Get(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEdpoint) fmt.Fprint(writer, string(raw)) }) diff --git a/cloud/servicedesk.go b/cloud/servicedesk.go index 0b9282d..7a385bf 100644 --- a/cloud/servicedesk.go +++ b/cloud/servicedesk.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "net/http" "github.com/google/go-querystring/query" ) @@ -27,7 +28,7 @@ func (s *ServiceDeskService) GetOrganizations(ctx context.Context, serviceDeskID apiEndPoint += fmt.Sprintf("&accountId=%s", accountID) } - req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -161,7 +162,7 @@ func (s *ServiceDeskService) RemoveCustomers(ctx context.Context, serviceDeskID // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-get func (s *ServiceDeskService) ListCustomers(ctx context.Context, serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/servicedesk_test.go b/cloud/servicedesk_test.go index a982cdb..043c1f3 100644 --- a/cloud/servicedesk_test.go +++ b/cloud/servicedesk_test.go @@ -15,7 +15,7 @@ func TestServiceDeskService_GetOrganizations(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/servicedesk/10001/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/10001/organization") w.WriteHeader(http.StatusOK) @@ -108,7 +108,7 @@ func TestServiceDeskServiceStringServiceDeskID_GetOrganizations(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/servicedesk/TEST/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/TEST/organization") w.WriteHeader(http.StatusOK) @@ -362,7 +362,7 @@ func TestServiceDeskService_ListCustomers(t *testing.T) { ) testMux.HandleFunc(fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID), func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID)) qs := r.URL.Query() diff --git a/cloud/sprint.go b/cloud/sprint.go index 1834067..ba9dd21 100644 --- a/cloud/sprint.go +++ b/cloud/sprint.go @@ -3,6 +3,7 @@ package cloud import ( "context" "fmt" + "net/http" "github.com/google/go-querystring/query" ) @@ -53,7 +54,7 @@ func (s *SprintService) MoveIssuesToSprint(ctx context.Context, sprintID int, is func (s *SprintService) GetIssuesForSprint(ctx context.Context, sprintID int) ([]Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err @@ -81,7 +82,7 @@ func (s *SprintService) GetIssuesForSprint(ctx context.Context, sprintID int) ([ func (s *SprintService) GetIssue(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/issue/%s", issueID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err diff --git a/cloud/sprint_test.go b/cloud/sprint_test.go index f12bd2b..4f33dbd 100644 --- a/cloud/sprint_test.go +++ b/cloud/sprint_test.go @@ -50,7 +50,7 @@ func TestSprintService_GetIssuesForSprint(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) @@ -75,7 +75,7 @@ func TestSprintService_GetIssue(t *testing.T) { testAPIEndpoint := "/rest/agile/1.0/issue/10002" testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"sprint": {"id": 37,"self": "http://www.example.com/jira/rest/agile/1.0/sprint/13", "state": "future", "name": "sprint 2"}, "epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) diff --git a/cloud/status.go b/cloud/status.go index 8d293d5..fe79666 100644 --- a/cloud/status.go +++ b/cloud/status.go @@ -1,6 +1,9 @@ package cloud -import "context" +import ( + "context" + "net/http" +) // StatusService handles staties for the Jira instance / API. // @@ -24,7 +27,7 @@ type Status struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-status-get func (s *StatusService) GetAllStatuses(ctx context.Context) ([]Status, *Response, error) { apiEndpoint := "rest/api/2/status" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err diff --git a/cloud/status_test.go b/cloud/status_test.go index 1e71957..c433a68 100644 --- a/cloud/status_test.go +++ b/cloud/status_test.go @@ -19,7 +19,7 @@ func TestStatusService_GetAllStatuses(t *testing.T) { } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) diff --git a/cloud/statuscategory.go b/cloud/statuscategory.go index eea97a4..74dc4c7 100644 --- a/cloud/statuscategory.go +++ b/cloud/statuscategory.go @@ -1,6 +1,9 @@ package cloud -import "context" +import ( + "context" + "net/http" +) // StatusCategoryService handles status categories for the Jira instance / API. // @@ -30,7 +33,7 @@ const ( // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, *Response, error) { apiEndpoint := "rest/api/2/statuscategory" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/statuscategory_test.go b/cloud/statuscategory_test.go index cad63fe..f05705a 100644 --- a/cloud/statuscategory_test.go +++ b/cloud/statuscategory_test.go @@ -18,7 +18,7 @@ func TestStatusCategoryService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) diff --git a/cloud/user.go b/cloud/user.go index ade589c..35972b2 100644 --- a/cloud/user.go +++ b/cloud/user.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "net/http" ) // UserService handles users for the Jira instance / API. @@ -49,7 +50,7 @@ type userSearchF func(userSearch) userSearch // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-get func (s *UserService) Get(ctx context.Context, accountId string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -68,7 +69,7 @@ func (s *UserService) Get(ctx context.Context, accountId string) (*User, *Respon // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser func (s *UserService) GetByAccountID(ctx context.Context, accountID string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -135,7 +136,7 @@ func (s *UserService) Delete(ctx context.Context, accountId string) (*Response, // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-groups-get func (s *UserService) GetGroups(ctx context.Context, accountId string) (*[]UserGroup, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?accountId=%s", accountId) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -153,7 +154,7 @@ func (s *UserService) GetGroups(ctx context.Context, accountId string) (*[]UserG // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-myself-get func (s *UserService) GetSelf(ctx context.Context) (*User, *Response, error) { const apiEndpoint = "rest/api/2/myself" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -242,7 +243,7 @@ func (s *UserService) Find(ctx context.Context, property string, tweaks ...userS } apiEndpoint := fmt.Sprintf("/rest/api/2/user/search?%s", queryString[:len(queryString)-1]) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/user_test.go b/cloud/user_test.go index 5964874..b465102 100644 --- a/cloud/user_test.go +++ b/cloud/user_test.go @@ -11,7 +11,7 @@ func TestUserService_Get_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","key":"fred", @@ -34,7 +34,7 @@ func TestUserService_GetByAccountID_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","accountId": "000000000000000000000000", @@ -104,7 +104,7 @@ func TestUserService_GetGroups(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/user/groups", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/user/groups?accountId=000000000000000000000000") w.WriteHeader(http.StatusCreated) @@ -122,7 +122,7 @@ func TestUserService_GetSelf(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/myself", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/myself") w.WriteHeader(http.StatusCreated) @@ -150,7 +150,7 @@ func TestUserService_Find_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/user/search", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/user/search?query=fred@example.com") fmt.Fprint(w, `[{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","key":"fred", @@ -173,7 +173,7 @@ func TestUserService_Find_SuccessParams(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/user/search", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/user/search?query=fred@example.com&startAt=100&maxResults=1000") fmt.Fprint(w, `[{"self":"http://www.example.com/jira/rest/api/2/user?query=fred","key":"fred", diff --git a/cloud/version.go b/cloud/version.go index aaad00b..8bd2066 100644 --- a/cloud/version.go +++ b/cloud/version.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "net/http" ) // VersionService handles Versions for the Jira instance / API. @@ -31,7 +32,7 @@ type Version struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-get func (s *VersionService) Get(ctx context.Context, versionID int) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/version/%v", versionID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/version_test.go b/cloud/version_test.go index 3cf52d0..1147b498b 100644 --- a/cloud/version_test.go +++ b/cloud/version_test.go @@ -11,7 +11,7 @@ func TestVersionService_Get_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/version/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/version/10002") fmt.Fprint(w, `{ diff --git a/onpremise/auth_transport_basic_test.go b/onpremise/auth_transport_basic_test.go index e85cf27..2c8b2e4 100644 --- a/onpremise/auth_transport_basic_test.go +++ b/onpremise/auth_transport_basic_test.go @@ -31,7 +31,7 @@ func TestBasicAuthTransport(t *testing.T) { } basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) - req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) + req, _ := basicAuthClient.NewRequest(context.Background(), http.MethodGet, ".", nil) basicAuthClient.Do(req, nil) } diff --git a/onpremise/auth_transport_cookie_test.go b/onpremise/auth_transport_cookie_test.go index dbf6b2b..076aadc 100644 --- a/onpremise/auth_transport_cookie_test.go +++ b/onpremise/auth_transport_cookie_test.go @@ -38,7 +38,7 @@ func TestCookieAuthTransport_SessionObject_Exists(t *testing.T) { } basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) - req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) + req, _ := basicAuthClient.NewRequest(context.Background(), http.MethodGet, ".", nil) basicAuthClient.Do(req, nil) } @@ -74,7 +74,7 @@ func TestCookieAuthTransport_SessionObject_ExistsWithEmptyCookie(t *testing.T) { } basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) - req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) + req, _ := basicAuthClient.NewRequest(context.Background(), http.MethodGet, ".", nil) basicAuthClient.Do(req, nil) } @@ -115,6 +115,6 @@ func TestCookieAuthTransport_SessionObject_DoesNotExist(t *testing.T) { } basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) - req, _ := basicAuthClient.NewRequest(context.Background(), "GET", ".", nil) + req, _ := basicAuthClient.NewRequest(context.Background(), http.MethodGet, ".", nil) basicAuthClient.Do(req, nil) } diff --git a/onpremise/authentication.go b/onpremise/authentication.go index 62c8b50..6c9fa86 100644 --- a/onpremise/authentication.go +++ b/onpremise/authentication.go @@ -159,7 +159,7 @@ func (s *AuthenticationService) GetCurrentUser(ctx context.Context) (*Session, e } apiEndpoint := "rest/auth/1/session" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, fmt.Errorf("could not create request for getting user info : %s", err) } diff --git a/onpremise/authentication_test.go b/onpremise/authentication_test.go index 7b602a4..1453529 100644 --- a/onpremise/authentication_test.go +++ b/onpremise/authentication_test.go @@ -155,8 +155,8 @@ func TestAuthenticationService_GetUserInfo_AccessForbidden_Fail(t *testing.T) { fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) } - if r.Method == "GET" { - testMethod(t, r, "GET") + if r.Method == http.MethodGet { + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/auth/1/session") w.WriteHeader(http.StatusForbidden) @@ -193,8 +193,8 @@ func TestAuthenticationService_GetUserInfo_NonOkStatusCode_Fail(t *testing.T) { fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) } - if r.Method == "GET" { - testMethod(t, r, "GET") + if r.Method == http.MethodGet { + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/auth/1/session") //any status but 200 w.WriteHeader(240) @@ -249,8 +249,8 @@ func TestAuthenticationService_GetUserInfo_Success(t *testing.T) { fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) } - if r.Method == "GET" { - testMethod(t, r, "GET") + if r.Method == http.MethodGet { + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/auth/1/session") fmt.Fprint(w, `{"self":"https://my.jira.com/rest/api/latest/user?username=foo","name":"foo","loginInfo":{"failedLoginCount":12,"loginCount":357,"lastFailedLoginTime":"2016-09-06T16:41:23.949+0200","previousLoginTime":"2016-09-07T11:36:23.476+0200"}}`) } diff --git a/onpremise/board.go b/onpremise/board.go index 019f4af..b521eb6 100644 --- a/onpremise/board.go +++ b/onpremise/board.go @@ -3,6 +3,7 @@ package onpremise import ( "context" "fmt" + "net/http" "strconv" "time" ) @@ -134,7 +135,7 @@ func (s *BoardService) GetAllBoards(ctx context.Context, opt *BoardListOptions) if err != nil { return nil, nil, err } - req, err := s.client.NewRequest(ctx, "GET", url, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, url, nil) if err != nil { return nil, nil, err } @@ -155,7 +156,7 @@ func (s *BoardService) GetAllBoards(ctx context.Context, opt *BoardListOptions) // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard func (s *BoardService) GetBoard(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -241,7 +242,7 @@ func (s *BoardService) GetAllSprintsWithOptions(ctx context.Context, boardID int if err != nil { return nil, nil, err } - req, err := s.client.NewRequest(ctx, "GET", url, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, url, nil) if err != nil { return nil, nil, err } @@ -260,7 +261,7 @@ func (s *BoardService) GetAllSprintsWithOptions(ctx context.Context, boardID int func (s *BoardService) GetBoardConfiguration(ctx context.Context, boardID int) (*BoardConfiguration, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/configuration", boardID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err diff --git a/onpremise/board_test.go b/onpremise/board_test.go index edfcb19..c05b345 100644 --- a/onpremise/board_test.go +++ b/onpremise/board_test.go @@ -18,7 +18,7 @@ func TestBoardService_GetAllBoards(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) @@ -43,7 +43,7 @@ func TestBoardService_GetAllBoards_WithFilter(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) testRequestParams(t, r, map[string]string{"type": "scrum", "name": "Test", "startAt": "1", "maxResults": "10", "projectKeyOrId": "TE"}) fmt.Fprint(w, string(raw)) @@ -72,7 +72,7 @@ func TestBoardService_GetBoard(t *testing.T) { testAPIEdpoint := "/rest/agile/1.0/board/1" testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, `{"id":4,"self":"https://test.jira.org/rest/agile/1.0/board/1","name":"Test Weekly","type":"scrum"}`) }) @@ -92,7 +92,7 @@ func TestBoardService_GetBoard_WrongID(t *testing.T) { testAPIEndpoint := "/rest/api/2/board/99999999" testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, nil) }) @@ -167,7 +167,7 @@ func TestBoardService_GetAllSprints(t *testing.T) { } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -199,7 +199,7 @@ func TestBoardService_GetAllSprintsWithOptions(t *testing.T) { } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -230,7 +230,7 @@ func TestBoardService_GetBoardConfigoration(t *testing.T) { } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) diff --git a/onpremise/error_test.go b/onpremise/error_test.go index 4aca07d..339b9dc 100644 --- a/onpremise/error_test.go +++ b/onpremise/error_test.go @@ -18,7 +18,7 @@ func TestError_NewJiraError(t *testing.T) { fmt.Fprint(w, `{"errorMessages":["Issue does not exist or you do not have permission to see it."],"errors":{}}`) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, errors.New("Original http error")) @@ -52,7 +52,7 @@ func TestError_NoJSON(t *testing.T) { fmt.Fprint(w, `Original message body`) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, errors.New("Original http error")) @@ -72,7 +72,7 @@ func TestError_Unauthorized_NilError(t *testing.T) { fmt.Fprint(w, `User is not authorized`) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, nil) @@ -91,7 +91,7 @@ func TestError_BadJSON(t *testing.T) { fmt.Fprint(w, `Not JSON`) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) resp, _ := testClient.Do(req, nil) err := NewJiraError(resp, errors.New("Original http error")) diff --git a/onpremise/examples/do/main.go b/onpremise/examples/do/main.go index ee9c15f..8f1a1c7 100644 --- a/onpremise/examples/do/main.go +++ b/onpremise/examples/do/main.go @@ -3,13 +3,14 @@ package main import ( "context" "fmt" + "net/http" jira "github.com/andygrunwald/go-jira/onpremise" ) func main() { jiraClient, _ := jira.NewClient("https://jira.atlassian.com/", nil) - req, _ := jiraClient.NewRequest(context.Background(), "GET", "/rest/api/2/project", nil) + req, _ := jiraClient.NewRequest(context.Background(), http.MethodGet, "/rest/api/2/project", nil) projects := new([]jira.Project) res, err := jiraClient.Do(req, projects) diff --git a/onpremise/field.go b/onpremise/field.go index 054d853..2bc2489 100644 --- a/onpremise/field.go +++ b/onpremise/field.go @@ -1,6 +1,9 @@ package onpremise -import "context" +import ( + "context" + "net/http" +) // FieldService handles fields for the Jira instance / API. // @@ -34,7 +37,7 @@ type FieldSchema struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get func (s *FieldService) GetList(ctx context.Context) ([]Field, *Response, error) { apiEndpoint := "rest/api/2/field" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/field_test.go b/onpremise/field_test.go index 59cf831..f89b46e 100644 --- a/onpremise/field_test.go +++ b/onpremise/field_test.go @@ -18,7 +18,7 @@ func TestFieldService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) diff --git a/onpremise/filter.go b/onpremise/filter.go index 6bf3074..d861f0a 100644 --- a/onpremise/filter.go +++ b/onpremise/filter.go @@ -3,6 +3,7 @@ package onpremise import ( "context" "fmt" + "net/http" "github.com/google/go-querystring/query" ) @@ -123,7 +124,7 @@ func (fs *FilterService) GetList(ctx context.Context) ([]*Filter, *Response, err options := &GetQueryOptions{} apiEndpoint := "rest/api/2/filter" - req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := fs.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -146,7 +147,7 @@ func (fs *FilterService) GetList(ctx context.Context) ([]*Filter, *Response, err // GetFavouriteList retrieves the user's favourited filters from Jira func (fs *FilterService) GetFavouriteList(ctx context.Context) ([]*Filter, *Response, error) { apiEndpoint := "rest/api/2/filter/favourite" - req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := fs.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -162,7 +163,7 @@ func (fs *FilterService) GetFavouriteList(ctx context.Context) ([]*Filter, *Resp // Get retrieves a single Filter from Jira func (fs *FilterService) Get(ctx context.Context, filterID int) (*Filter, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/filter/%d", filterID) - req, err := fs.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := fs.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -185,7 +186,7 @@ func (fs *FilterService) GetMyFilters(ctx context.Context, opts *GetMyFiltersQue if err != nil { return nil, nil, err } - req, err := fs.client.NewRequest(ctx, "GET", url, nil) + req, err := fs.client.NewRequest(ctx, http.MethodGet, url, nil) if err != nil { return nil, nil, err } @@ -208,7 +209,7 @@ func (fs *FilterService) Search(ctx context.Context, opt *FilterSearchOptions) ( if err != nil { return nil, nil, err } - req, err := fs.client.NewRequest(ctx, "GET", url, nil) + req, err := fs.client.NewRequest(ctx, http.MethodGet, url, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/filter_test.go b/onpremise/filter_test.go index d3d9c91..55af7fc 100644 --- a/onpremise/filter_test.go +++ b/onpremise/filter_test.go @@ -17,7 +17,7 @@ func TestFilterService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEndpoint) fmt.Fprint(writer, string(raw)) }) @@ -40,7 +40,7 @@ func TestFilterService_Get(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEndpoint) fmt.Fprint(writer, string(raw)) }) @@ -64,7 +64,7 @@ func TestFilterService_GetFavouriteList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEndpoint) fmt.Fprint(writer, string(raw)) }) @@ -87,7 +87,7 @@ func TestFilterService_GetMyFilters(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEndpoint) fmt.Fprint(writer, string(raw)) }) @@ -111,7 +111,7 @@ func TestFilterService_Search(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEndpoint) fmt.Fprint(writer, string(raw)) }) diff --git a/onpremise/group.go b/onpremise/group.go index 98e984c..e373668 100644 --- a/onpremise/group.go +++ b/onpremise/group.go @@ -3,6 +3,7 @@ package onpremise import ( "context" "fmt" + "net/http" "net/url" ) @@ -66,7 +67,7 @@ type GroupSearchOptions struct { // WARNING: This API only returns the first page of group members func (s *GroupService) Get(ctx context.Context, name string) ([]GroupMember, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -98,7 +99,7 @@ func (s *GroupService) GetWithOptions(ctx context.Context, name string, options options.IncludeInactiveUsers, ) } - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/group_test.go b/onpremise/group_test.go index 8f7e176..f9b923e 100644 --- a/onpremise/group_test.go +++ b/onpremise/group_test.go @@ -11,7 +11,7 @@ func TestGroupService_Get(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/group/member", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/group/member?groupname=default") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/group/member?includeInactiveUsers=false&maxResults=50&groupname=default&startAt=0","maxResults":50,"startAt":0,"total":2,"isLast":true,"values":[{"self":"http://www.example.com/jira/rest/api/2/user?username=michael","name":"michael","key":"michael","emailAddress":"michael@example.com","displayName":"MichaelScofield","active":true,"timeZone":"Australia/Sydney"},{"self":"http://www.example.com/jira/rest/api/2/user?username=alex","name":"alex","key":"alex","emailAddress":"alex@example.com","displayName":"AlexanderMahone","active":true,"timeZone":"Australia/Sydney"}]}`) }) @@ -26,7 +26,7 @@ func TestGroupService_GetPage(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/group/member", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/group/member?groupname=default") startAt := r.URL.Query().Get("startAt") if startAt == "0" { diff --git a/onpremise/issue.go b/onpremise/issue.go index dee42b9..e100670 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -615,7 +615,7 @@ type RemoteLinkStatus struct { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getIssue func (s *IssueService) Get(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -644,7 +644,7 @@ func (s *IssueService) Get(ctx context.Context, issueID string, options *GetQuer // Caller must close resp.Body. func (s *IssueService) DownloadAttachment(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("secure/attachment/%s/", attachmentID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, err } @@ -741,7 +741,7 @@ func (s *IssueService) DeleteLink(ctx context.Context, linkID string) (*Response func (s *IssueService) GetWorklogs(ctx context.Context, issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -1027,7 +1027,7 @@ func (s *IssueService) Search(ctx context.Context, jql string, options *SearchOp u.RawQuery = uv.Encode() - req, err := s.client.NewRequest(ctx, "GET", u.String(), nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, u.String(), nil) if err != nil { return []Issue{}, nil, err } @@ -1087,7 +1087,7 @@ func (s *IssueService) SearchPages(ctx context.Context, jql string, options *Sea // GetCustomFields returns a map of customfield_* keys with string values func (s *IssueService) GetCustomFields(ctx context.Context, issueID string) (CustomFields, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -1127,7 +1127,7 @@ func (s *IssueService) GetCustomFields(ctx context.Context, issueID string) (Cus // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getTransitions func (s *IssueService) GetTransitions(ctx context.Context, id string) ([]Transition, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions?expand=transitions.fields", id) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -1280,7 +1280,7 @@ func (s *IssueService) Delete(ctx context.Context, issueID string) (*Response, e func (s *IssueService) GetWatchers(ctx context.Context, issueID string) (*[]User, *Response, error) { watchesAPIEndpoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) - req, err := s.client.NewRequest(ctx, "GET", watchesAPIEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, watchesAPIEndpoint, nil) if err != nil { return nil, nil, err } @@ -1381,7 +1381,7 @@ func (c ChangelogHistory) CreatedTime() (time.Time, error) { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getRemoteIssueLinks func (s *IssueService) GetRemoteLinks(ctx context.Context, id string) (*[]RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", id) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/issue_test.go b/onpremise/issue_test.go index f1d7c76..eeda9ef 100644 --- a/onpremise/issue_test.go +++ b/onpremise/issue_test.go @@ -20,7 +20,7 @@ func TestIssueService_Get_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) @@ -39,7 +39,7 @@ func TestIssueService_Get_WithQuerySuccess(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002?expand=foo") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) @@ -108,7 +108,7 @@ func TestIssueService_CreateThenGet(t *testing.T) { } testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") bytes, err := json.Marshal(issue) @@ -339,7 +339,7 @@ func TestIssueService_Get_Fields(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) @@ -369,7 +369,7 @@ func TestIssueService_Get_RenderedFields(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{},"renderedFields":{"resolutiondate":"In 1 week","updated":"2 hours ago","comment":{"comments":[{"body":"This is HTML"}]}}}`) @@ -402,7 +402,7 @@ func TestIssueService_DownloadAttachment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/secure/attachment/", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/secure/attachment/10000/") w.WriteHeader(http.StatusOK) @@ -437,7 +437,7 @@ func TestIssueService_DownloadAttachment_BadStatus(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/secure/attachment/", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/secure/attachment/10000/") w.WriteHeader(http.StatusForbidden) @@ -621,7 +621,7 @@ func TestIssueService_Search(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/search?expand=foo&jql=type+%3D+Bug+and+Status+NOT+IN+%28Resolved%29&maxResults=40&startAt=1") w.WriteHeader(http.StatusOK) fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 40,"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}}}]}`) @@ -652,7 +652,7 @@ func TestIssueService_SearchEmptyJQL(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/search?expand=foo&maxResults=40&startAt=1") w.WriteHeader(http.StatusOK) fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 40,"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}}}]}`) @@ -683,7 +683,7 @@ func TestIssueService_Search_WithoutPaging(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/search?jql=something") 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}}}]}`) @@ -712,7 +712,7 @@ func TestIssueService_SearchPages(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) if r.URL.String() == "/rest/api/2/search?expand=foo&jql=something&maxResults=2&startAt=1&validateQuery=warn" { w.WriteHeader(http.StatusOK) fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 2,"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}}}]}`) @@ -750,7 +750,7 @@ func TestIssueService_SearchPages_EmptyResult(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) if r.URL.String() == "/rest/api/2/search?expand=foo&jql=something&maxResults=50&startAt=1&validateQuery=warn" { w.WriteHeader(http.StatusOK) // This is what Jira outputs when the &maxResult= issue occurs. It used to cause SearchPages to go into an endless loop. @@ -778,7 +778,7 @@ func TestIssueService_GetCustomFields(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"customfield_123":"test","watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) @@ -800,7 +800,7 @@ func TestIssueService_GetComplexCustomFields(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"customfield_123":{"self":"http://www.example.com/jira/rest/api/2/customFieldOption/123","value":"test","id":"123"},"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) @@ -830,7 +830,7 @@ func TestIssueService_GetTransitions(t *testing.T) { } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -1559,7 +1559,7 @@ func TestIssueService_GetWorklogs(t *testing.T) { t.Run(tc.name, func(t *testing.T) { uri := fmt.Sprintf(tc.uri, tc.issueId) testMux.HandleFunc(uri, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, uri) _, _ = fmt.Fprint(w, tc.response) }) @@ -1588,14 +1588,14 @@ func TestIssueService_GetWatchers(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002/watchers", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002/watchers") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","accountId": "000000000000000000000000","displayName":"Fred F. User","active":false}]}`) }) testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","key":"fred","accountId": "000000000000000000000000", @@ -1629,14 +1629,14 @@ func TestIssueService_DeprecatedGetWatchers(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002/watchers", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002/watchers") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000", "accountId": "000000000000000000000000", "displayName":"Fred F. User","active":false}]}`) }) testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000", "accountId": "000000000000000000000000", "key": "", "name": "", "emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", @@ -1691,7 +1691,7 @@ func TestIssueService_Get_Fields_Changelog(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"changelog","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","changelog":{"startAt": 0,"maxResults": 1, "total": 1, "histories": [{"id": "10002", "author": {"self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "key": "fred", "emailAddress": "fred@example.com", "avatarUrls": {"48x48": "http://www.example.com/secure/useravatar?ownerId=fred&avatarId=33072", "24x24": "http://www.example.com/secure/useravatar?size=small&ownerId=fred&avatarId=33072", "16x16": "http://www.example.com/secure/useravatar?size=xsmall&ownerId=fred&avatarId=33072", "32x32": "http://www.example.com/secure/useravatar?size=medium&ownerId=fred&avatarId=33072"},"displayName":"Fred","active": true,"timeZone":"Australia/Sydney"},"created":"2018-06-20T16:50:35.000+0300","items":[{"field":"Rank","fieldtype":"custom","from":"","fromString":"","to":"","toString":"Ranked higher"}]}]}}`) @@ -1722,7 +1722,7 @@ func TestIssueService_Get_Transitions(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/api/latest/issue/10002","key":"EX-1","transitions":[{"id":"121","name":"Start","to":{"self":"http://www.example.com/rest/api/2/status/10444","description":"","iconUrl":"http://www.example.com/images/icons/statuses/inprogress.png","name":"In progress","id":"10444","statusCategory":{"self":"http://www.example.com/rest/api/2/statuscategory/4","id":4,"key":"indeterminate","colorName":"yellow","name":"In Progress"}}}]}`) @@ -1753,7 +1753,7 @@ func TestIssueService_Get_Fields_AffectsVersions(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"fields":{"versions":[{"self":"http://www.example.com/jira/rest/api/2/version/10705","id":"10705","description":"test description","name":"2.1.0-rc3","archived":false,"released":false,"releaseDate":"2018-09-30"}]}}`) @@ -1794,7 +1794,7 @@ func TestIssueService_GetRemoteLinks(t *testing.T) { } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) diff --git a/onpremise/issuelinktype.go b/onpremise/issuelinktype.go index 0589247..9aee11a 100644 --- a/onpremise/issuelinktype.go +++ b/onpremise/issuelinktype.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "net/http" ) // IssueLinkTypeService handles issue link types for the Jira instance / API. @@ -17,7 +18,7 @@ type IssueLinkTypeService service // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-get func (s *IssueLinkTypeService) GetList(ctx context.Context) ([]IssueLinkType, *Response, error) { apiEndpoint := "rest/api/2/issueLinkType" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -35,7 +36,7 @@ func (s *IssueLinkTypeService) GetList(ctx context.Context) ([]IssueLinkType, *R // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-get func (s *IssueLinkTypeService) Get(ctx context.Context, ID string) (*IssueLinkType, *Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) - req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/issuelinktype_test.go b/onpremise/issuelinktype_test.go index 9181632..2a1e53c 100644 --- a/onpremise/issuelinktype_test.go +++ b/onpremise/issuelinktype_test.go @@ -18,7 +18,7 @@ func TestIssueLinkTypeService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -36,7 +36,7 @@ func TestIssueLinkTypeService_Get(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issueLinkType/123", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/issueLinkType/123") fmt.Fprint(w, `{"id": "123","name": "Blocked","inward": "Blocked","outward": "Blocked", diff --git a/onpremise/jira_test.go b/onpremise/jira_test.go index e78fc70..5116f84 100644 --- a/onpremise/jira_test.go +++ b/onpremise/jira_test.go @@ -165,7 +165,7 @@ func TestClient_NewRequest(t *testing.T) { inURL, outURL := "rest/api/2/issue/", testJiraInstanceURL+"rest/api/2/issue/" inBody, outBody := &Issue{Key: "MESOS"}, `{"key":"MESOS"}`+"\n" - req, _ := c.NewRequest(context.Background(), "GET", inURL, inBody) + req, _ := c.NewRequest(context.Background(), http.MethodGet, inURL, inBody) // Test that relative URL was expanded if got, want := req.URL.String(), outURL; got != want { @@ -189,7 +189,7 @@ func TestClient_NewRawRequest(t *testing.T) { outBody := `{"key":"MESOS"}` + "\n" inBody := outBody - req, _ := c.NewRawRequest(context.Background(), "GET", inURL, strings.NewReader(outBody)) + req, _ := c.NewRawRequest(context.Background(), http.MethodGet, inURL, strings.NewReader(outBody)) // Test that relative URL was expanded if got, want := req.URL.String(), outURL; got != want { @@ -217,7 +217,7 @@ func TestClient_NewRequest_BadURL(t *testing.T) { if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } - _, err = c.NewRequest(context.Background(), "GET", ":", nil) + _, err = c.NewRequest(context.Background(), http.MethodGet, ":", nil) testURLParseError(t, err) } @@ -233,7 +233,7 @@ func TestClient_NewRequest_SessionCookies(t *testing.T) { inURL := "rest/api/2/issue/" inBody := &Issue{Key: "MESOS"} - req, err := c.NewRequest(context.Background(), "GET", inURL, inBody) + req, err := c.NewRequest(context.Background(), http.MethodGet, inURL, inBody) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) @@ -260,7 +260,7 @@ func TestClient_NewRequest_BasicAuth(t *testing.T) { inURL := "rest/api/2/issue/" inBody := &Issue{Key: "MESOS"} - req, err := c.NewRequest(context.Background(), "GET", inURL, inBody) + req, err := c.NewRequest(context.Background(), http.MethodGet, inURL, inBody) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) @@ -281,7 +281,7 @@ func TestClient_NewRequest_EmptyBody(t *testing.T) { if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } - req, err := c.NewRequest(context.Background(), "GET", "/", nil) + req, err := c.NewRequest(context.Background(), http.MethodGet, "/", nil) if err != nil { t.Fatalf("NewRequest returned unexpected error: %v", err) } @@ -302,7 +302,7 @@ func TestClient_NewMultiPartRequest(t *testing.T) { inURL := "rest/api/2/issue/" inBuf := bytes.NewBufferString("teststring") - req, err := c.NewMultiPartRequest(context.Background(), "GET", inURL, inBuf) + req, err := c.NewMultiPartRequest(context.Background(), http.MethodGet, inURL, inBuf) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) @@ -333,7 +333,7 @@ func TestClient_NewMultiPartRequest_BasicAuth(t *testing.T) { inURL := "rest/api/2/issue/" inBuf := bytes.NewBufferString("teststring") - req, err := c.NewMultiPartRequest(context.Background(), "GET", inURL, inBuf) + req, err := c.NewMultiPartRequest(context.Background(), http.MethodGet, inURL, inBuf) if err != nil { t.Errorf("An error occurred. Expected nil. Got %+v.", err) @@ -358,13 +358,13 @@ func TestClient_Do(t *testing.T) { } testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if m := "GET"; m != r.Method { + if m := http.MethodGet; m != r.Method { t.Errorf("Request method = %v, want %v", r.Method, m) } fmt.Fprint(w, `{"A":"a"}`) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) body := new(foo) testClient.Do(req, body) @@ -379,13 +379,13 @@ func TestClient_Do_HTTPResponse(t *testing.T) { defer teardown() testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if m := "GET"; m != r.Method { + if m := http.MethodGet; m != r.Method { t.Errorf("Request method = %v, want %v", r.Method, m) } fmt.Fprint(w, `{"A":"a"}`) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) res, _ := testClient.Do(req, nil) _, err := io.ReadAll(res.Body) @@ -404,7 +404,7 @@ func TestClient_Do_HTTPError(t *testing.T) { http.Error(w, "Bad Request", 400) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) _, err := testClient.Do(req, nil) if err == nil { @@ -422,7 +422,7 @@ func TestClient_Do_RedirectLoop(t *testing.T) { http.Redirect(w, r, "/", http.StatusFound) }) - req, _ := testClient.NewRequest(context.Background(), "GET", "/", nil) + req, _ := testClient.NewRequest(context.Background(), http.MethodGet, "/", nil) _, err := testClient.Do(req, nil) if err == nil { diff --git a/onpremise/metaissue.go b/onpremise/metaissue.go index 48d65d6..5a5e970 100644 --- a/onpremise/metaissue.go +++ b/onpremise/metaissue.go @@ -3,6 +3,7 @@ package onpremise import ( "context" "fmt" + "net/http" "strings" "github.com/google/go-querystring/query" @@ -57,7 +58,7 @@ func (s *IssueService) GetCreateMeta(ctx context.Context, projectkeys string) (* func (s *IssueService) GetCreateMetaWithOptions(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { apiEndpoint := "rest/api/2/issue/createmeta" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -83,7 +84,7 @@ func (s *IssueService) GetCreateMetaWithOptions(ctx context.Context, options *Ge func (s *IssueService) GetEditMeta(ctx context.Context, issue *Issue) (*EditMetaInfo, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/issue/%s/editmeta", issue.Key) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/metaissue_test.go b/onpremise/metaissue_test.go index 605cd52..9a5c22d 100644 --- a/onpremise/metaissue_test.go +++ b/onpremise/metaissue_test.go @@ -15,7 +15,7 @@ func TestIssueService_GetCreateMeta_Success(t *testing.T) { testAPIEndpoint := "/rest/api/2/issue/createmeta" testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, `{ @@ -387,7 +387,7 @@ func TestIssueService_GetEditMeta_Success(t *testing.T) { testAPIEndpoint := "/rest/api/2/issue/PROJ-9001/editmeta" testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, `{ @@ -464,7 +464,7 @@ func TestMetaIssueType_GetCreateMetaWithOptions(t *testing.T) { testAPIEndpoint := "/rest/api/2/issue/createmeta" testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, `{ diff --git a/onpremise/organization.go b/onpremise/organization.go index dafa7a7..7854256 100644 --- a/onpremise/organization.go +++ b/onpremise/organization.go @@ -3,6 +3,7 @@ package onpremise import ( "context" "fmt" + "net/http" ) // OrganizationService handles Organizations for the Jira instance / API. @@ -66,7 +67,7 @@ func (s *OrganizationService) GetAllOrganizations(ctx context.Context, start int apiEndPoint += fmt.Sprintf("&accountId=%s", accountID) } - req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -121,7 +122,7 @@ func (s *OrganizationService) CreateOrganization(ctx context.Context, name strin func (s *OrganizationService) GetOrganization(ctx context.Context, organizationID int) (*Organization, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) - req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -172,7 +173,7 @@ func (s *OrganizationService) DeleteOrganization(ctx context.Context, organizati func (s *OrganizationService) GetPropertiesKeys(ctx context.Context, organizationID int) (*PropertyKeys, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property", organizationID) - req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -197,7 +198,7 @@ func (s *OrganizationService) GetPropertiesKeys(ctx context.Context, organizatio func (s *OrganizationService) GetProperty(ctx context.Context, organizationID int, propertyKey string) (*EntityProperty, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) - req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -272,7 +273,7 @@ func (s *OrganizationService) DeleteProperty(ctx context.Context, organizationID func (s *OrganizationService) GetUsers(ctx context.Context, organizationID int, start int, limit int) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user?start=%d&limit=%d", organizationID, start, limit) - req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { diff --git a/onpremise/organization_test.go b/onpremise/organization_test.go index fa72e72..c83cf31 100644 --- a/onpremise/organization_test.go +++ b/onpremise/organization_test.go @@ -12,7 +12,7 @@ func TestOrganizationService_GetAllOrganizations(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/servicedeskapi/organization") w.WriteHeader(http.StatusOK) @@ -63,7 +63,7 @@ func TestOrganizationService_GetOrganization(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/servicedeskapi/organization/1") w.WriteHeader(http.StatusOK) @@ -105,7 +105,7 @@ func TestOrganizationService_GetPropertiesKeys(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1/property", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/servicedeskapi/organization/1/property") w.WriteHeader(http.StatusOK) @@ -136,7 +136,7 @@ func TestOrganizationService_GetProperty(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1/property/organization.attributes", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/servicedeskapi/organization/1/property/organization.attributes") w.WriteHeader(http.StatusOK) @@ -203,7 +203,7 @@ func TestOrganizationService_GetUsers(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/servicedeskapi/organization/1/user") w.WriteHeader(http.StatusOK) diff --git a/onpremise/permissionscheme.go b/onpremise/permissionscheme.go index 8f7d337..0f5f29d 100644 --- a/onpremise/permissionscheme.go +++ b/onpremise/permissionscheme.go @@ -3,6 +3,7 @@ package onpremise import ( "context" "fmt" + "net/http" ) // PermissionSchemeService handles permissionschemes for the Jira instance / API. @@ -32,7 +33,7 @@ type Holder struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-get func (s *PermissionSchemeService) GetList(ctx context.Context) (*PermissionSchemes, *Response, error) { apiEndpoint := "/rest/api/3/permissionscheme" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -52,7 +53,7 @@ func (s *PermissionSchemeService) GetList(ctx context.Context) (*PermissionSchem // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-schemeId-get func (s *PermissionSchemeService) Get(ctx context.Context, schemeID int) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/3/permissionscheme/%d", schemeID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/permissionscheme_test.go b/onpremise/permissionscheme_test.go index 8cd8c3e..ace1aa9 100644 --- a/onpremise/permissionscheme_test.go +++ b/onpremise/permissionscheme_test.go @@ -18,7 +18,7 @@ func TestPermissionSchemeService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -46,7 +46,7 @@ func TestPermissionSchemeService_GetList_NoList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -69,7 +69,7 @@ func TestPermissionSchemeService_Get(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEdpoint) fmt.Fprint(writer, string(raw)) }) @@ -92,7 +92,7 @@ func TestPermissionSchemeService_Get_NoScheme(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEdpoint) fmt.Fprint(writer, string(raw)) }) diff --git a/onpremise/priority.go b/onpremise/priority.go index 8e928ae..dd0ff7e 100644 --- a/onpremise/priority.go +++ b/onpremise/priority.go @@ -1,6 +1,9 @@ package onpremise -import "context" +import ( + "context" + "net/http" +) // PriorityService handles priorities for the Jira instance / API. // @@ -23,7 +26,7 @@ type Priority struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-priority-get func (s *PriorityService) GetList(ctx context.Context) ([]Priority, *Response, error) { apiEndpoint := "rest/api/2/priority" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/priority_test.go b/onpremise/priority_test.go index 5c15bc4..91edd49 100644 --- a/onpremise/priority_test.go +++ b/onpremise/priority_test.go @@ -18,7 +18,7 @@ func TestPriorityService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) diff --git a/onpremise/project.go b/onpremise/project.go index b9d72e1..364893f 100644 --- a/onpremise/project.go +++ b/onpremise/project.go @@ -3,6 +3,7 @@ package onpremise import ( "context" "fmt" + "net/http" "github.com/google/go-querystring/query" ) @@ -92,7 +93,7 @@ func (s *ProjectService) GetList(ctx context.Context) (*ProjectList, *Response, // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects func (s *ProjectService) ListWithOptions(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { apiEndpoint := "rest/api/2/project" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -122,7 +123,7 @@ func (s *ProjectService) ListWithOptions(ctx context.Context, options *GetQueryO // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject func (s *ProjectService) Get(ctx context.Context, projectID string) (*Project, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/project/%s", projectID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -144,7 +145,7 @@ func (s *ProjectService) Get(ctx context.Context, projectID string) (*Project, * // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject func (s *ProjectService) GetPermissionScheme(ctx context.Context, projectID string) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s/permissionscheme", projectID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/project_test.go b/onpremise/project_test.go index 655222f..2725d54 100644 --- a/onpremise/project_test.go +++ b/onpremise/project_test.go @@ -18,7 +18,7 @@ func TestProjectService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) @@ -42,7 +42,7 @@ func TestProjectService_ListWithOptions(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/project?expand=issueTypes") fmt.Fprint(w, string(raw)) }) @@ -66,7 +66,7 @@ func TestProjectService_Get(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) @@ -90,7 +90,7 @@ func TestProjectService_Get_NoProject(t *testing.T) { testAPIEdpoint := "/rest/api/2/project/99999999" testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, nil) }) @@ -114,7 +114,7 @@ func TestProjectService_GetPermissionScheme_Failure(t *testing.T) { testAPIEdpoint := "/rest/api/2/project/99999999/permissionscheme" testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, nil) }) @@ -138,7 +138,7 @@ func TestProjectService_GetPermissionScheme_Success(t *testing.T) { testAPIEdpoint := "/rest/api/2/project/99999999/permissionscheme" testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, `{ "expand": "permissions,user,group,projectRole,field,all", diff --git a/onpremise/resolution.go b/onpremise/resolution.go index c7f4859..0dc007a 100644 --- a/onpremise/resolution.go +++ b/onpremise/resolution.go @@ -1,6 +1,9 @@ package onpremise -import "context" +import ( + "context" + "net/http" +) // ResolutionService handles resolutions for the Jira instance / API. // @@ -21,7 +24,7 @@ type Resolution struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-resolution-get func (s *ResolutionService) GetList(ctx context.Context) ([]Resolution, *Response, error) { apiEndpoint := "rest/api/2/resolution" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/resolution_test.go b/onpremise/resolution_test.go index ebaa0ef..ce33192 100644 --- a/onpremise/resolution_test.go +++ b/onpremise/resolution_test.go @@ -18,7 +18,7 @@ func TestResolutionService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) diff --git a/onpremise/role.go b/onpremise/role.go index b72c0c7..08c6dc6 100644 --- a/onpremise/role.go +++ b/onpremise/role.go @@ -3,6 +3,7 @@ package onpremise import ( "context" "fmt" + "net/http" ) // RoleService handles roles for the Jira instance / API. @@ -39,7 +40,7 @@ type ActorUser struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-get func (s *RoleService) GetList(ctx context.Context) (*[]Role, *Response, error) { apiEndpoint := "rest/api/3/role" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -57,7 +58,7 @@ func (s *RoleService) GetList(ctx context.Context) (*[]Role, *Response, error) { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-id-get func (s *RoleService) Get(ctx context.Context, roleID int) (*Role, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/3/role/%d", roleID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/role_test.go b/onpremise/role_test.go index fba7bab..8762bdd 100644 --- a/onpremise/role_test.go +++ b/onpremise/role_test.go @@ -19,7 +19,7 @@ func TestRoleService_GetList_NoList(t *testing.T) { } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -43,7 +43,7 @@ func TestRoleService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -70,7 +70,7 @@ func TestRoleService_Get_NoRole(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEdpoint) fmt.Fprint(writer, string(raw)) }) @@ -93,7 +93,7 @@ func TestRoleService_Get(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { - testMethod(t, request, "GET") + testMethod(t, request, http.MethodGet) testRequestURL(t, request, testAPIEdpoint) fmt.Fprint(writer, string(raw)) }) diff --git a/onpremise/servicedesk.go b/onpremise/servicedesk.go index d1c86ac..beeb1dc 100644 --- a/onpremise/servicedesk.go +++ b/onpremise/servicedesk.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "net/http" "github.com/google/go-querystring/query" ) @@ -27,7 +28,7 @@ func (s *ServiceDeskService) GetOrganizations(ctx context.Context, serviceDeskID apiEndPoint += fmt.Sprintf("&accountId=%s", accountID) } - req, err := s.client.NewRequest(ctx, "GET", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -161,7 +162,7 @@ func (s *ServiceDeskService) RemoveCustomers(ctx context.Context, serviceDeskID // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-get func (s *ServiceDeskService) ListCustomers(ctx context.Context, serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/servicedesk_test.go b/onpremise/servicedesk_test.go index 532a477..778292d 100644 --- a/onpremise/servicedesk_test.go +++ b/onpremise/servicedesk_test.go @@ -15,7 +15,7 @@ func TestServiceDeskService_GetOrganizations(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/servicedesk/10001/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/10001/organization") w.WriteHeader(http.StatusOK) @@ -108,7 +108,7 @@ func TestServiceDeskServiceStringServiceDeskID_GetOrganizations(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/servicedesk/TEST/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/TEST/organization") w.WriteHeader(http.StatusOK) @@ -362,7 +362,7 @@ func TestServiceDeskService_ListCustomers(t *testing.T) { ) testMux.HandleFunc(fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID), func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID)) qs := r.URL.Query() diff --git a/onpremise/sprint.go b/onpremise/sprint.go index 25011af..ff4ff53 100644 --- a/onpremise/sprint.go +++ b/onpremise/sprint.go @@ -3,6 +3,7 @@ package onpremise import ( "context" "fmt" + "net/http" "github.com/google/go-querystring/query" ) @@ -53,7 +54,7 @@ func (s *SprintService) MoveIssuesToSprint(ctx context.Context, sprintID int, is func (s *SprintService) GetIssuesForSprint(ctx context.Context, sprintID int) ([]Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err @@ -81,7 +82,7 @@ func (s *SprintService) GetIssuesForSprint(ctx context.Context, sprintID int) ([ func (s *SprintService) GetIssue(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/issue/%s", issueID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err diff --git a/onpremise/sprint_test.go b/onpremise/sprint_test.go index d063a69..a399b01 100644 --- a/onpremise/sprint_test.go +++ b/onpremise/sprint_test.go @@ -50,7 +50,7 @@ func TestSprintService_GetIssuesForSprint(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) @@ -75,7 +75,7 @@ func TestSprintService_GetIssue(t *testing.T) { testAPIEndpoint := "/rest/agile/1.0/issue/10002" testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"sprint": {"id": 37,"self": "http://www.example.com/jira/rest/agile/1.0/sprint/13", "state": "future", "name": "sprint 2"}, "epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) diff --git a/onpremise/status.go b/onpremise/status.go index f3e94f2..3d9a92c 100644 --- a/onpremise/status.go +++ b/onpremise/status.go @@ -1,6 +1,9 @@ package onpremise -import "context" +import ( + "context" + "net/http" +) // StatusService handles staties for the Jira instance / API. // @@ -24,7 +27,7 @@ type Status struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-status-get func (s *StatusService) GetAllStatuses(ctx context.Context) ([]Status, *Response, error) { apiEndpoint := "rest/api/2/status" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err diff --git a/onpremise/status_test.go b/onpremise/status_test.go index a5a9a2f..255d59b 100644 --- a/onpremise/status_test.go +++ b/onpremise/status_test.go @@ -19,7 +19,7 @@ func TestStatusService_GetAllStatuses(t *testing.T) { } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) diff --git a/onpremise/statuscategory.go b/onpremise/statuscategory.go index 7e7c8ce..3178c63 100644 --- a/onpremise/statuscategory.go +++ b/onpremise/statuscategory.go @@ -1,6 +1,9 @@ package onpremise -import "context" +import ( + "context" + "net/http" +) // StatusCategoryService handles status categories for the Jira instance / API. // @@ -30,7 +33,7 @@ const ( // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, *Response, error) { apiEndpoint := "rest/api/2/statuscategory" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/statuscategory_test.go b/onpremise/statuscategory_test.go index aee35ef..93af9d7 100644 --- a/onpremise/statuscategory_test.go +++ b/onpremise/statuscategory_test.go @@ -18,7 +18,7 @@ func TestStatusCategoryService_GetList(t *testing.T) { t.Error(err.Error()) } testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEdpoint) fmt.Fprint(w, string(raw)) }) diff --git a/onpremise/user.go b/onpremise/user.go index 79003be..d9f05e7 100644 --- a/onpremise/user.go +++ b/onpremise/user.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "net/http" ) // UserService handles users for the Jira instance / API. @@ -49,7 +50,7 @@ type userSearchF func(userSearch) userSearch // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-get func (s *UserService) Get(ctx context.Context, accountId string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -68,7 +69,7 @@ func (s *UserService) Get(ctx context.Context, accountId string) (*User, *Respon // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser func (s *UserService) GetByAccountID(ctx context.Context, accountID string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -135,7 +136,7 @@ func (s *UserService) Delete(ctx context.Context, accountId string) (*Response, // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-groups-get func (s *UserService) GetGroups(ctx context.Context, accountId string) (*[]UserGroup, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?accountId=%s", accountId) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -153,7 +154,7 @@ func (s *UserService) GetGroups(ctx context.Context, accountId string) (*[]UserG // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-myself-get func (s *UserService) GetSelf(ctx context.Context) (*User, *Response, error) { const apiEndpoint = "rest/api/2/myself" - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } @@ -242,7 +243,7 @@ func (s *UserService) Find(ctx context.Context, property string, tweaks ...userS } apiEndpoint := fmt.Sprintf("/rest/api/2/user/search?%s", queryString[:len(queryString)-1]) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/user_test.go b/onpremise/user_test.go index 97a4d39..5fd0108 100644 --- a/onpremise/user_test.go +++ b/onpremise/user_test.go @@ -11,7 +11,7 @@ func TestUserService_Get_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","key":"fred", @@ -34,7 +34,7 @@ func TestUserService_GetByAccountID_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","accountId": "000000000000000000000000", @@ -104,7 +104,7 @@ func TestUserService_GetGroups(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/user/groups", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/user/groups?accountId=000000000000000000000000") w.WriteHeader(http.StatusCreated) @@ -122,7 +122,7 @@ func TestUserService_GetSelf(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/myself", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/myself") w.WriteHeader(http.StatusCreated) @@ -150,7 +150,7 @@ func TestUserService_Find_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/user/search", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/user/search?query=fred@example.com") fmt.Fprint(w, `[{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","key":"fred", @@ -173,7 +173,7 @@ func TestUserService_Find_SuccessParams(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/user/search", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/user/search?query=fred@example.com&startAt=100&maxResults=1000") fmt.Fprint(w, `[{"self":"http://www.example.com/jira/rest/api/2/user?query=fred","key":"fred", diff --git a/onpremise/version.go b/onpremise/version.go index d1edc02..026e1e8 100644 --- a/onpremise/version.go +++ b/onpremise/version.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "net/http" ) // VersionService handles Versions for the Jira instance / API. @@ -31,7 +32,7 @@ type Version struct { // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-get func (s *VersionService) Get(ctx context.Context, versionID int) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/version/%v", versionID) - req, err := s.client.NewRequest(ctx, "GET", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/version_test.go b/onpremise/version_test.go index a43f14d..0bddae9 100644 --- a/onpremise/version_test.go +++ b/onpremise/version_test.go @@ -11,7 +11,7 @@ func TestVersionService_Get_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/version/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/version/10002") fmt.Fprint(w, `{ From d9332d8b71121392ee354bbbf9a5d7cfa9cbb717 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 21:21:46 +0200 Subject: [PATCH 043/189] Replaced "POST" with http.MethodPost Related: #508 --- cloud/auth_transport_cookie.go | 2 +- cloud/authentication.go | 2 +- cloud/authentication_test.go | 20 ++++++++++---------- cloud/board.go | 2 +- cloud/board_test.go | 2 +- cloud/component.go | 7 +++++-- cloud/component_test.go | 2 +- cloud/customer_test.go | 2 +- cloud/group.go | 2 +- cloud/group_test.go | 2 +- cloud/issue.go | 16 ++++++++-------- cloud/issue_test.go | 24 ++++++++++++------------ cloud/issuelinktype.go | 2 +- cloud/issuelinktype_test.go | 2 +- cloud/organization.go | 4 ++-- cloud/organization_test.go | 4 ++-- cloud/request.go | 5 +++-- cloud/request_test.go | 4 ++-- cloud/servicedesk.go | 4 ++-- cloud/servicedesk_test.go | 6 +++--- cloud/sprint.go | 2 +- cloud/sprint_test.go | 2 +- cloud/user.go | 2 +- cloud/user_test.go | 2 +- cloud/version.go | 2 +- cloud/version_test.go | 2 +- onpremise/auth_transport_cookie.go | 2 +- onpremise/authentication.go | 2 +- onpremise/authentication_test.go | 20 ++++++++++---------- onpremise/board.go | 2 +- onpremise/board_test.go | 2 +- onpremise/component.go | 7 +++++-- onpremise/component_test.go | 2 +- onpremise/customer_test.go | 2 +- onpremise/group.go | 2 +- onpremise/group_test.go | 2 +- onpremise/issue.go | 16 ++++++++-------- onpremise/issue_test.go | 24 ++++++++++++------------ onpremise/issuelinktype.go | 2 +- onpremise/issuelinktype_test.go | 2 +- onpremise/organization.go | 4 ++-- onpremise/organization_test.go | 4 ++-- onpremise/request.go | 5 +++-- onpremise/request_test.go | 4 ++-- onpremise/servicedesk.go | 4 ++-- onpremise/servicedesk_test.go | 6 +++--- onpremise/sprint.go | 2 +- onpremise/sprint_test.go | 2 +- onpremise/user.go | 2 +- onpremise/user_test.go | 2 +- onpremise/version.go | 2 +- onpremise/version_test.go | 2 +- 52 files changed, 130 insertions(+), 122 deletions(-) diff --git a/cloud/auth_transport_cookie.go b/cloud/auth_transport_cookie.go index ddde946..26565b5 100644 --- a/cloud/auth_transport_cookie.go +++ b/cloud/auth_transport_cookie.go @@ -90,7 +90,7 @@ func (t *CookieAuthTransport) buildAuthRequest() (*http.Request, error) { json.NewEncoder(b).Encode(body) // TODO Use a context here - req, err := http.NewRequest("POST", t.AuthURL, b) + req, err := http.NewRequest(http.MethodPost, t.AuthURL, b) if err != nil { return nil, err } diff --git a/cloud/authentication.go b/cloud/authentication.go index 9ec36fa..166d5fd 100644 --- a/cloud/authentication.go +++ b/cloud/authentication.go @@ -67,7 +67,7 @@ func (s *AuthenticationService) AcquireSessionCookie(ctx context.Context, userna password, } - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, body) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, body) if err != nil { return false, err } diff --git a/cloud/authentication_test.go b/cloud/authentication_test.go index 0f748d4..19bd5e5 100644 --- a/cloud/authentication_test.go +++ b/cloud/authentication_test.go @@ -14,7 +14,7 @@ func TestAuthenticationService_AcquireSessionCookie_Failure(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/auth/1/session") b, err := io.ReadAll(r.Body) if err != nil { @@ -48,7 +48,7 @@ func TestAuthenticationService_AcquireSessionCookie_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/auth/1/session") b, err := io.ReadAll(r.Body) if err != nil { @@ -138,8 +138,8 @@ func TestAuthenticationService_GetUserInfo_AccessForbidden_Fail(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == "POST" { - testMethod(t, r, "POST") + if r.Method == http.MethodPost { + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/auth/1/session") b, err := io.ReadAll(r.Body) if err != nil { @@ -176,8 +176,8 @@ func TestAuthenticationService_GetUserInfo_NonOkStatusCode_Fail(t *testing.T) { defer teardown() testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == "POST" { - testMethod(t, r, "POST") + if r.Method == http.MethodPost { + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/auth/1/session") b, err := io.ReadAll(r.Body) if err != nil { @@ -232,8 +232,8 @@ func TestAuthenticationService_GetUserInfo_Success(t *testing.T) { testUserInfo.LoginInfo.PreviousLoginTime = "2016-09-07T11:36:23.476+0200" testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == "POST" { - testMethod(t, r, "POST") + if r.Method == http.MethodPost { + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/auth/1/session") b, err := io.ReadAll(r.Body) if err != nil { @@ -274,8 +274,8 @@ func TestAuthenticationService_Logout_Success(t *testing.T) { defer teardown() testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == "POST" { - testMethod(t, r, "POST") + if r.Method == http.MethodPost { + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/auth/1/session") b, err := io.ReadAll(r.Body) if err != nil { diff --git a/cloud/board.go b/cloud/board.go index 1d69dbd..0893062 100644 --- a/cloud/board.go +++ b/cloud/board.go @@ -181,7 +181,7 @@ func (s *BoardService) GetBoard(ctx context.Context, boardID int) (*Board, *Resp // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard func (s *BoardService) CreateBoard(ctx context.Context, board *Board) (*Board, *Response, error) { apiEndpoint := "rest/agile/1.0/board" - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, board) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, board) if err != nil { return nil, nil, err } diff --git a/cloud/board_test.go b/cloud/board_test.go index a3a05e1..c190256 100644 --- a/cloud/board_test.go +++ b/cloud/board_test.go @@ -114,7 +114,7 @@ func TestBoardService_CreateBoard(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/agile/1.0/board", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/agile/1.0/board") w.WriteHeader(http.StatusCreated) diff --git a/cloud/component.go b/cloud/component.go index 04fc131..ea97461 100644 --- a/cloud/component.go +++ b/cloud/component.go @@ -1,6 +1,9 @@ package cloud -import "context" +import ( + "context" + "net/http" +) // ComponentService handles components for the Jira instance / API.// // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.1/#api/2/component @@ -21,7 +24,7 @@ type CreateComponentOptions struct { // Create creates a new Jira component based on the given options. func (s *ComponentService) Create(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) { apiEndpoint := "rest/api/2/component" - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, options) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, options) if err != nil { return nil, nil, err } diff --git a/cloud/component_test.go b/cloud/component_test.go index 15bcbbc..780cfdb 100644 --- a/cloud/component_test.go +++ b/cloud/component_test.go @@ -11,7 +11,7 @@ func TestComponentService_Create_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/component", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/component") w.WriteHeader(http.StatusCreated) diff --git a/cloud/customer_test.go b/cloud/customer_test.go index bce6a12..ecbfee9 100644 --- a/cloud/customer_test.go +++ b/cloud/customer_test.go @@ -17,7 +17,7 @@ func TestCustomerService_Create(t *testing.T) { ) testMux.HandleFunc("/rest/servicedeskapi/customer", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/servicedeskapi/customer") w.WriteHeader(http.StatusOK) diff --git a/cloud/group.go b/cloud/group.go index bfd2a5c..49f02c1 100644 --- a/cloud/group.go +++ b/cloud/group.go @@ -121,7 +121,7 @@ func (s *GroupService) Add(ctx context.Context, groupname string, username strin Name string `json:"name"` } user.Name = username - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, &user) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, &user) if err != nil { return nil, nil, err } diff --git a/cloud/group_test.go b/cloud/group_test.go index cc9fdfe..c6acefb 100644 --- a/cloud/group_test.go +++ b/cloud/group_test.go @@ -81,7 +81,7 @@ func TestGroupService_Add(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/group/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/group/user?groupname=default") w.WriteHeader(http.StatusCreated) diff --git a/cloud/issue.go b/cloud/issue.go index f500e2a..444de79 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -678,7 +678,7 @@ func (s *IssueService) PostAttachment(ctx context.Context, issueID string, r io. } writer.Close() - req, err := s.client.NewMultiPartRequest(ctx, "POST", apiEndpoint, b) + req, err := s.client.NewMultiPartRequest(ctx, http.MethodPost, apiEndpoint, b) if err != nil { return nil, nil, err } @@ -781,7 +781,7 @@ func WithQueryOptions(options interface{}) func(*http.Request) error { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-createIssues func (s *IssueService) Create(ctx context.Context, issue *Issue) (*Issue, *Response, error) { apiEndpoint := "rest/api/2/issue" - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, issue) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, issue) if err != nil { return nil, nil, err } @@ -863,7 +863,7 @@ func (s *IssueService) UpdateIssue(ctx context.Context, jiraID string, data map[ // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-addComment func (s *IssueService) AddComment(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment", issueID) - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, comment) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, comment) if err != nil { return nil, nil, err } @@ -927,7 +927,7 @@ func (s *IssueService) DeleteComment(ctx context.Context, issueID, commentID str // https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-worklog-post func (s *IssueService) AddWorklogRecord(ctx context.Context, issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, record) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, record) if err != nil { return nil, nil, err } @@ -982,7 +982,7 @@ func (s *IssueService) UpdateWorklogRecord(ctx context.Context, issueID, worklog // Caller must close resp.Body func (s *IssueService) AddLink(ctx context.Context, issueLink *IssueLink) (*Response, error) { apiEndpoint := "rest/api/2/issueLink" - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, issueLink) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, issueLink) if err != nil { return nil, err } @@ -1161,7 +1161,7 @@ func (s *IssueService) DoTransition(ctx context.Context, ticketID, transitionID func (s *IssueService) DoTransitionWithPayload(ctx context.Context, ticketID, payload interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions", ticketID) - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, payload) if err != nil { return nil, err } @@ -1313,7 +1313,7 @@ func (s *IssueService) GetWatchers(ctx context.Context, issueID string) (*[]User func (s *IssueService) AddWatcher(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) - req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, userName) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndPoint, userName) if err != nil { return nil, err } @@ -1399,7 +1399,7 @@ func (s *IssueService) GetRemoteLinks(ctx context.Context, id string) (*[]Remote // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-remotelink-post func (s *IssueService) AddRemoteLink(ctx context.Context, issueID string, remotelink *RemoteLink) (*RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", issueID) - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, remotelink) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, remotelink) if err != nil { return nil, nil, err } diff --git a/cloud/issue_test.go b/cloud/issue_test.go index 476b408..8a4086e 100644 --- a/cloud/issue_test.go +++ b/cloud/issue_test.go @@ -61,7 +61,7 @@ func TestIssueService_Create(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue") w.WriteHeader(http.StatusCreated) @@ -86,7 +86,7 @@ func TestIssueService_CreateThenGet(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue") w.WriteHeader(http.StatusCreated) @@ -182,7 +182,7 @@ func TestIssueService_AddComment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/comment", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/comment") w.WriteHeader(http.StatusCreated) @@ -254,7 +254,7 @@ func TestIssueService_AddWorklogRecord(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/worklog", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/worklog") w.WriteHeader(http.StatusCreated) @@ -298,7 +298,7 @@ func TestIssueService_AddLink(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issueLink", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issueLink") w.WriteHeader(http.StatusOK) @@ -464,7 +464,7 @@ func TestIssueService_PostAttachment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") status := http.StatusOK @@ -513,7 +513,7 @@ func TestIssueService_PostAttachment_NoResponse(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") w.WriteHeader(http.StatusOK) }) @@ -532,7 +532,7 @@ func TestIssueService_PostAttachment_NoFilename(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") w.WriteHeader(http.StatusOK) fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) @@ -550,7 +550,7 @@ func TestIssueService_PostAttachment_NoAttachment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") w.WriteHeader(http.StatusOK) fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) @@ -863,7 +863,7 @@ func TestIssueService_DoTransition(t *testing.T) { transitionID := "22" testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, testAPIEndpoint) decoder := json.NewDecoder(r.Body) @@ -908,7 +908,7 @@ func TestIssueService_DoTransitionWithPayload(t *testing.T) { } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, testAPIEndpoint) decoder := json.NewDecoder(r.Body) @@ -1822,7 +1822,7 @@ func TestIssueService_AddRemoteLink(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/remotelink", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/remotelink") w.WriteHeader(http.StatusCreated) diff --git a/cloud/issuelinktype.go b/cloud/issuelinktype.go index bee53e1..704421e 100644 --- a/cloud/issuelinktype.go +++ b/cloud/issuelinktype.go @@ -54,7 +54,7 @@ func (s *IssueLinkTypeService) Get(ctx context.Context, ID string) (*IssueLinkTy // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-post func (s *IssueLinkTypeService) Create(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := "/rest/api/2/issueLinkType" - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, linkType) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, linkType) if err != nil { return nil, nil, err } diff --git a/cloud/issuelinktype_test.go b/cloud/issuelinktype_test.go index c9a3e59..3b0ad07 100644 --- a/cloud/issuelinktype_test.go +++ b/cloud/issuelinktype_test.go @@ -54,7 +54,7 @@ func TestIssueLinkTypeService_Create(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issueLinkType", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issueLinkType") w.WriteHeader(http.StatusCreated) diff --git a/cloud/organization.go b/cloud/organization.go index 2f18316..cce347a 100644 --- a/cloud/organization.go +++ b/cloud/organization.go @@ -95,7 +95,7 @@ func (s *OrganizationService) CreateOrganization(ctx context.Context, name strin Name: name, } - req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, organization) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndPoint, organization) req.Header.Set("Accept", "application/json") if err != nil { @@ -297,7 +297,7 @@ func (s *OrganizationService) GetUsers(ctx context.Context, organizationID int, func (s *OrganizationService) AddUsers(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) - req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, users) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndPoint, users) if err != nil { return nil, err diff --git a/cloud/organization_test.go b/cloud/organization_test.go index 01feba6..471e507 100644 --- a/cloud/organization_test.go +++ b/cloud/organization_test.go @@ -36,7 +36,7 @@ func TestOrganizationService_CreateOrganization(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/servicedeskapi/organization") o := new(OrganizationCreationDTO) @@ -283,7 +283,7 @@ func TestOrganizationService_AddUsers(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/servicedeskapi/organization/1/user") w.WriteHeader(http.StatusNoContent) diff --git a/cloud/request.go b/cloud/request.go index 286b223..c6a1e80 100644 --- a/cloud/request.go +++ b/cloud/request.go @@ -3,6 +3,7 @@ package cloud import ( "context" "fmt" + "net/http" ) // RequestService handles ServiceDesk customer requests for the Jira instance / API. @@ -76,7 +77,7 @@ func (r *RequestService) Create(ctx context.Context, requester string, participa payload.FieldValues[field.FieldID] = field.Value } - req, err := r.client.NewRequest(ctx, "POST", apiEndpoint, payload) + req, err := r.client.NewRequest(ctx, http.MethodPost, apiEndpoint, payload) if err != nil { return nil, nil, err } @@ -96,7 +97,7 @@ func (r *RequestService) Create(ctx context.Context, requester string, participa func (r *RequestService) CreateComment(ctx context.Context, issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/request/%v/comment", issueIDOrKey) - req, err := r.client.NewRequest(ctx, "POST", apiEndpoint, comment) + req, err := r.client.NewRequest(ctx, http.MethodPost, apiEndpoint, comment) if err != nil { return nil, nil, err } diff --git a/cloud/request_test.go b/cloud/request_test.go index bc7d7bd..3cfdb61 100644 --- a/cloud/request_test.go +++ b/cloud/request_test.go @@ -23,7 +23,7 @@ func TestRequestService_Create(t *testing.T) { ) testMux.HandleFunc("/rest/servicedeskapi/request", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/servicedeskapi/request") var payload struct { @@ -146,7 +146,7 @@ func TestRequestService_CreateComment(t *testing.T) { defer teardown() testMux.HandleFunc("/rest/servicedeskapi/request/HELPDESK-1/comment", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/servicedeskapi/request/HELPDESK-1/comment") w.Write([]byte(`{ diff --git a/cloud/servicedesk.go b/cloud/servicedesk.go index 7a385bf..ef243b3 100644 --- a/cloud/servicedesk.go +++ b/cloud/servicedesk.go @@ -59,7 +59,7 @@ func (s *ServiceDeskService) AddOrganization(ctx context.Context, serviceDeskID OrganizationID: organizationID, } - req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, organization) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndPoint, organization) if err != nil { return nil, err @@ -114,7 +114,7 @@ func (s *ServiceDeskService) AddCustomers(ctx context.Context, serviceDeskID int }{ AccountIDs: acountIDs, } - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, payload) if err != nil { return nil, err } diff --git a/cloud/servicedesk_test.go b/cloud/servicedesk_test.go index 043c1f3..8a8f683 100644 --- a/cloud/servicedesk_test.go +++ b/cloud/servicedesk_test.go @@ -74,7 +74,7 @@ func TestServiceDeskService_AddOrganizations(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/servicedesk/10001/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/10001/organization") w.WriteHeader(http.StatusNoContent) @@ -167,7 +167,7 @@ func TestServiceDeskServiceStringServiceDeskID_AddOrganizations(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/servicedesk/TEST/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/TEST/organization") w.WriteHeader(http.StatusNoContent) @@ -227,7 +227,7 @@ func TestServiceDeskService_AddCustomers(t *testing.T) { ) testMux.HandleFunc(fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID), func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID)) var payload struct { diff --git a/cloud/sprint.go b/cloud/sprint.go index ba9dd21..407fe40 100644 --- a/cloud/sprint.go +++ b/cloud/sprint.go @@ -33,7 +33,7 @@ func (s *SprintService) MoveIssuesToSprint(ctx context.Context, sprintID int, is payload := IssuesWrapper{Issues: issueIDs} - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, payload) if err != nil { return nil, err diff --git a/cloud/sprint_test.go b/cloud/sprint_test.go index 4f33dbd..67801ad 100644 --- a/cloud/sprint_test.go +++ b/cloud/sprint_test.go @@ -19,7 +19,7 @@ func TestSprintService_MoveIssuesToSprint(t *testing.T) { issuesToMove := []string{"KEY-1", "KEY-2"} testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, testAPIEndpoint) decoder := json.NewDecoder(r.Body) diff --git a/cloud/user.go b/cloud/user.go index 35972b2..4c2691c 100644 --- a/cloud/user.go +++ b/cloud/user.go @@ -87,7 +87,7 @@ func (s *UserService) GetByAccountID(ctx context.Context, accountID string) (*Us // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-createUser func (s *UserService) Create(ctx context.Context, user *User) (*User, *Response, error) { apiEndpoint := "/rest/api/2/user" - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, user) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, user) if err != nil { return nil, nil, err } diff --git a/cloud/user_test.go b/cloud/user_test.go index b465102..0f18db9 100644 --- a/cloud/user_test.go +++ b/cloud/user_test.go @@ -57,7 +57,7 @@ func TestUserService_Create(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/user") w.WriteHeader(http.StatusCreated) diff --git a/cloud/version.go b/cloud/version.go index 8bd2066..6c79df2 100644 --- a/cloud/version.go +++ b/cloud/version.go @@ -50,7 +50,7 @@ func (s *VersionService) Get(ctx context.Context, versionID int) (*Version, *Res // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-post func (s *VersionService) Create(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := "/rest/api/2/version" - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, version) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, version) if err != nil { return nil, nil, err } diff --git a/cloud/version_test.go b/cloud/version_test.go index 1147b498b..4a1b6c2 100644 --- a/cloud/version_test.go +++ b/cloud/version_test.go @@ -42,7 +42,7 @@ func TestVersionService_Create(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/version", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/version") w.WriteHeader(http.StatusCreated) diff --git a/onpremise/auth_transport_cookie.go b/onpremise/auth_transport_cookie.go index c63104e..89ac2a0 100644 --- a/onpremise/auth_transport_cookie.go +++ b/onpremise/auth_transport_cookie.go @@ -90,7 +90,7 @@ func (t *CookieAuthTransport) buildAuthRequest() (*http.Request, error) { json.NewEncoder(b).Encode(body) // TODO Use a context here - req, err := http.NewRequest("POST", t.AuthURL, b) + req, err := http.NewRequest(http.MethodPost, t.AuthURL, b) if err != nil { return nil, err } diff --git a/onpremise/authentication.go b/onpremise/authentication.go index 6c9fa86..be5d402 100644 --- a/onpremise/authentication.go +++ b/onpremise/authentication.go @@ -67,7 +67,7 @@ func (s *AuthenticationService) AcquireSessionCookie(ctx context.Context, userna password, } - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, body) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, body) if err != nil { return false, err } diff --git a/onpremise/authentication_test.go b/onpremise/authentication_test.go index 1453529..295de5a 100644 --- a/onpremise/authentication_test.go +++ b/onpremise/authentication_test.go @@ -14,7 +14,7 @@ func TestAuthenticationService_AcquireSessionCookie_Failure(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/auth/1/session") b, err := io.ReadAll(r.Body) if err != nil { @@ -48,7 +48,7 @@ func TestAuthenticationService_AcquireSessionCookie_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/auth/1/session") b, err := io.ReadAll(r.Body) if err != nil { @@ -138,8 +138,8 @@ func TestAuthenticationService_GetUserInfo_AccessForbidden_Fail(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == "POST" { - testMethod(t, r, "POST") + if r.Method == http.MethodPost { + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/auth/1/session") b, err := io.ReadAll(r.Body) if err != nil { @@ -176,8 +176,8 @@ func TestAuthenticationService_GetUserInfo_NonOkStatusCode_Fail(t *testing.T) { defer teardown() testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == "POST" { - testMethod(t, r, "POST") + if r.Method == http.MethodPost { + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/auth/1/session") b, err := io.ReadAll(r.Body) if err != nil { @@ -232,8 +232,8 @@ func TestAuthenticationService_GetUserInfo_Success(t *testing.T) { testUserInfo.LoginInfo.PreviousLoginTime = "2016-09-07T11:36:23.476+0200" testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == "POST" { - testMethod(t, r, "POST") + if r.Method == http.MethodPost { + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/auth/1/session") b, err := io.ReadAll(r.Body) if err != nil { @@ -274,8 +274,8 @@ func TestAuthenticationService_Logout_Success(t *testing.T) { defer teardown() testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == "POST" { - testMethod(t, r, "POST") + if r.Method == http.MethodPost { + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/auth/1/session") b, err := io.ReadAll(r.Body) if err != nil { diff --git a/onpremise/board.go b/onpremise/board.go index b521eb6..7326b8c 100644 --- a/onpremise/board.go +++ b/onpremise/board.go @@ -181,7 +181,7 @@ func (s *BoardService) GetBoard(ctx context.Context, boardID int) (*Board, *Resp // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard func (s *BoardService) CreateBoard(ctx context.Context, board *Board) (*Board, *Response, error) { apiEndpoint := "rest/agile/1.0/board" - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, board) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, board) if err != nil { return nil, nil, err } diff --git a/onpremise/board_test.go b/onpremise/board_test.go index c05b345..baf575d 100644 --- a/onpremise/board_test.go +++ b/onpremise/board_test.go @@ -114,7 +114,7 @@ func TestBoardService_CreateBoard(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/agile/1.0/board", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/agile/1.0/board") w.WriteHeader(http.StatusCreated) diff --git a/onpremise/component.go b/onpremise/component.go index 3d10d6d..a1b841b 100644 --- a/onpremise/component.go +++ b/onpremise/component.go @@ -1,6 +1,9 @@ package onpremise -import "context" +import ( + "context" + "net/http" +) // ComponentService handles components for the Jira instance / API.// // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.1/#api/2/component @@ -21,7 +24,7 @@ type CreateComponentOptions struct { // Create creates a new Jira component based on the given options. func (s *ComponentService) Create(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) { apiEndpoint := "rest/api/2/component" - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, options) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, options) if err != nil { return nil, nil, err } diff --git a/onpremise/component_test.go b/onpremise/component_test.go index f7f4f66..28aefaf 100644 --- a/onpremise/component_test.go +++ b/onpremise/component_test.go @@ -11,7 +11,7 @@ func TestComponentService_Create_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/component", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/component") w.WriteHeader(http.StatusCreated) diff --git a/onpremise/customer_test.go b/onpremise/customer_test.go index 835381c..9cebf77 100644 --- a/onpremise/customer_test.go +++ b/onpremise/customer_test.go @@ -17,7 +17,7 @@ func TestCustomerService_Create(t *testing.T) { ) testMux.HandleFunc("/rest/servicedeskapi/customer", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/servicedeskapi/customer") w.WriteHeader(http.StatusOK) diff --git a/onpremise/group.go b/onpremise/group.go index e373668..ff2a73e 100644 --- a/onpremise/group.go +++ b/onpremise/group.go @@ -121,7 +121,7 @@ func (s *GroupService) Add(ctx context.Context, groupname string, username strin Name string `json:"name"` } user.Name = username - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, &user) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, &user) if err != nil { return nil, nil, err } diff --git a/onpremise/group_test.go b/onpremise/group_test.go index f9b923e..a963908 100644 --- a/onpremise/group_test.go +++ b/onpremise/group_test.go @@ -81,7 +81,7 @@ func TestGroupService_Add(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/group/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/group/user?groupname=default") w.WriteHeader(http.StatusCreated) diff --git a/onpremise/issue.go b/onpremise/issue.go index e100670..9b26e9e 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -678,7 +678,7 @@ func (s *IssueService) PostAttachment(ctx context.Context, issueID string, r io. } writer.Close() - req, err := s.client.NewMultiPartRequest(ctx, "POST", apiEndpoint, b) + req, err := s.client.NewMultiPartRequest(ctx, http.MethodPost, apiEndpoint, b) if err != nil { return nil, nil, err } @@ -781,7 +781,7 @@ func WithQueryOptions(options interface{}) func(*http.Request) error { // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-createIssues func (s *IssueService) Create(ctx context.Context, issue *Issue) (*Issue, *Response, error) { apiEndpoint := "rest/api/2/issue" - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, issue) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, issue) if err != nil { return nil, nil, err } @@ -863,7 +863,7 @@ func (s *IssueService) UpdateIssue(ctx context.Context, jiraID string, data map[ // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-addComment func (s *IssueService) AddComment(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment", issueID) - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, comment) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, comment) if err != nil { return nil, nil, err } @@ -927,7 +927,7 @@ func (s *IssueService) DeleteComment(ctx context.Context, issueID, commentID str // https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-worklog-post func (s *IssueService) AddWorklogRecord(ctx context.Context, issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, record) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, record) if err != nil { return nil, nil, err } @@ -982,7 +982,7 @@ func (s *IssueService) UpdateWorklogRecord(ctx context.Context, issueID, worklog // Caller must close resp.Body func (s *IssueService) AddLink(ctx context.Context, issueLink *IssueLink) (*Response, error) { apiEndpoint := "rest/api/2/issueLink" - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, issueLink) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, issueLink) if err != nil { return nil, err } @@ -1161,7 +1161,7 @@ func (s *IssueService) DoTransition(ctx context.Context, ticketID, transitionID func (s *IssueService) DoTransitionWithPayload(ctx context.Context, ticketID, payload interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions", ticketID) - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, payload) if err != nil { return nil, err } @@ -1313,7 +1313,7 @@ func (s *IssueService) GetWatchers(ctx context.Context, issueID string) (*[]User func (s *IssueService) AddWatcher(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) - req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, userName) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndPoint, userName) if err != nil { return nil, err } @@ -1399,7 +1399,7 @@ func (s *IssueService) GetRemoteLinks(ctx context.Context, id string) (*[]Remote // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-remotelink-post func (s *IssueService) AddRemoteLink(ctx context.Context, issueID string, remotelink *RemoteLink) (*RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", issueID) - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, remotelink) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, remotelink) if err != nil { return nil, nil, err } diff --git a/onpremise/issue_test.go b/onpremise/issue_test.go index eeda9ef..ee58b78 100644 --- a/onpremise/issue_test.go +++ b/onpremise/issue_test.go @@ -61,7 +61,7 @@ func TestIssueService_Create(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue") w.WriteHeader(http.StatusCreated) @@ -86,7 +86,7 @@ func TestIssueService_CreateThenGet(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue") w.WriteHeader(http.StatusCreated) @@ -182,7 +182,7 @@ func TestIssueService_AddComment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/comment", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/comment") w.WriteHeader(http.StatusCreated) @@ -254,7 +254,7 @@ func TestIssueService_AddWorklogRecord(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/worklog", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/worklog") w.WriteHeader(http.StatusCreated) @@ -298,7 +298,7 @@ func TestIssueService_AddLink(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issueLink", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issueLink") w.WriteHeader(http.StatusOK) @@ -464,7 +464,7 @@ func TestIssueService_PostAttachment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") status := http.StatusOK @@ -513,7 +513,7 @@ func TestIssueService_PostAttachment_NoResponse(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") w.WriteHeader(http.StatusOK) }) @@ -532,7 +532,7 @@ func TestIssueService_PostAttachment_NoFilename(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") w.WriteHeader(http.StatusOK) fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) @@ -550,7 +550,7 @@ func TestIssueService_PostAttachment_NoAttachment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") w.WriteHeader(http.StatusOK) fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) @@ -863,7 +863,7 @@ func TestIssueService_DoTransition(t *testing.T) { transitionID := "22" testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, testAPIEndpoint) decoder := json.NewDecoder(r.Body) @@ -908,7 +908,7 @@ func TestIssueService_DoTransitionWithPayload(t *testing.T) { } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, testAPIEndpoint) decoder := json.NewDecoder(r.Body) @@ -1822,7 +1822,7 @@ func TestIssueService_AddRemoteLink(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/remotelink", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/remotelink") w.WriteHeader(http.StatusCreated) diff --git a/onpremise/issuelinktype.go b/onpremise/issuelinktype.go index 9aee11a..8697f26 100644 --- a/onpremise/issuelinktype.go +++ b/onpremise/issuelinktype.go @@ -54,7 +54,7 @@ func (s *IssueLinkTypeService) Get(ctx context.Context, ID string) (*IssueLinkTy // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-post func (s *IssueLinkTypeService) Create(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := "/rest/api/2/issueLinkType" - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, linkType) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, linkType) if err != nil { return nil, nil, err } diff --git a/onpremise/issuelinktype_test.go b/onpremise/issuelinktype_test.go index 2a1e53c..4d20c22 100644 --- a/onpremise/issuelinktype_test.go +++ b/onpremise/issuelinktype_test.go @@ -54,7 +54,7 @@ func TestIssueLinkTypeService_Create(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issueLinkType", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issueLinkType") w.WriteHeader(http.StatusCreated) diff --git a/onpremise/organization.go b/onpremise/organization.go index 7854256..406b48c 100644 --- a/onpremise/organization.go +++ b/onpremise/organization.go @@ -95,7 +95,7 @@ func (s *OrganizationService) CreateOrganization(ctx context.Context, name strin Name: name, } - req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, organization) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndPoint, organization) req.Header.Set("Accept", "application/json") if err != nil { @@ -297,7 +297,7 @@ func (s *OrganizationService) GetUsers(ctx context.Context, organizationID int, func (s *OrganizationService) AddUsers(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) - req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, users) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndPoint, users) if err != nil { return nil, err diff --git a/onpremise/organization_test.go b/onpremise/organization_test.go index c83cf31..5726f96 100644 --- a/onpremise/organization_test.go +++ b/onpremise/organization_test.go @@ -36,7 +36,7 @@ func TestOrganizationService_CreateOrganization(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/servicedeskapi/organization") o := new(OrganizationCreationDTO) @@ -283,7 +283,7 @@ func TestOrganizationService_AddUsers(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/servicedeskapi/organization/1/user") w.WriteHeader(http.StatusNoContent) diff --git a/onpremise/request.go b/onpremise/request.go index 1514979..6d12705 100644 --- a/onpremise/request.go +++ b/onpremise/request.go @@ -3,6 +3,7 @@ package onpremise import ( "context" "fmt" + "net/http" ) // RequestService handles ServiceDesk customer requests for the Jira instance / API. @@ -76,7 +77,7 @@ func (r *RequestService) Create(ctx context.Context, requester string, participa payload.FieldValues[field.FieldID] = field.Value } - req, err := r.client.NewRequest(ctx, "POST", apiEndpoint, payload) + req, err := r.client.NewRequest(ctx, http.MethodPost, apiEndpoint, payload) if err != nil { return nil, nil, err } @@ -96,7 +97,7 @@ func (r *RequestService) Create(ctx context.Context, requester string, participa func (r *RequestService) CreateComment(ctx context.Context, issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/request/%v/comment", issueIDOrKey) - req, err := r.client.NewRequest(ctx, "POST", apiEndpoint, comment) + req, err := r.client.NewRequest(ctx, http.MethodPost, apiEndpoint, comment) if err != nil { return nil, nil, err } diff --git a/onpremise/request_test.go b/onpremise/request_test.go index fcee64e..0adc634 100644 --- a/onpremise/request_test.go +++ b/onpremise/request_test.go @@ -23,7 +23,7 @@ func TestRequestService_Create(t *testing.T) { ) testMux.HandleFunc("/rest/servicedeskapi/request", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/servicedeskapi/request") var payload struct { @@ -146,7 +146,7 @@ func TestRequestService_CreateComment(t *testing.T) { defer teardown() testMux.HandleFunc("/rest/servicedeskapi/request/HELPDESK-1/comment", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/servicedeskapi/request/HELPDESK-1/comment") w.Write([]byte(`{ diff --git a/onpremise/servicedesk.go b/onpremise/servicedesk.go index beeb1dc..2f4844e 100644 --- a/onpremise/servicedesk.go +++ b/onpremise/servicedesk.go @@ -59,7 +59,7 @@ func (s *ServiceDeskService) AddOrganization(ctx context.Context, serviceDeskID OrganizationID: organizationID, } - req, err := s.client.NewRequest(ctx, "POST", apiEndPoint, organization) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndPoint, organization) if err != nil { return nil, err @@ -114,7 +114,7 @@ func (s *ServiceDeskService) AddCustomers(ctx context.Context, serviceDeskID int }{ AccountIDs: acountIDs, } - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, payload) if err != nil { return nil, err } diff --git a/onpremise/servicedesk_test.go b/onpremise/servicedesk_test.go index 778292d..34002aa 100644 --- a/onpremise/servicedesk_test.go +++ b/onpremise/servicedesk_test.go @@ -74,7 +74,7 @@ func TestServiceDeskService_AddOrganizations(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/servicedesk/10001/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/10001/organization") w.WriteHeader(http.StatusNoContent) @@ -167,7 +167,7 @@ func TestServiceDeskServiceStringServiceDeskID_AddOrganizations(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/servicedesk/TEST/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/TEST/organization") w.WriteHeader(http.StatusNoContent) @@ -227,7 +227,7 @@ func TestServiceDeskService_AddCustomers(t *testing.T) { ) testMux.HandleFunc(fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID), func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID)) var payload struct { diff --git a/onpremise/sprint.go b/onpremise/sprint.go index ff4ff53..145f0e9 100644 --- a/onpremise/sprint.go +++ b/onpremise/sprint.go @@ -33,7 +33,7 @@ func (s *SprintService) MoveIssuesToSprint(ctx context.Context, sprintID int, is payload := IssuesWrapper{Issues: issueIDs} - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, payload) if err != nil { return nil, err diff --git a/onpremise/sprint_test.go b/onpremise/sprint_test.go index a399b01..4b99575 100644 --- a/onpremise/sprint_test.go +++ b/onpremise/sprint_test.go @@ -19,7 +19,7 @@ func TestSprintService_MoveIssuesToSprint(t *testing.T) { issuesToMove := []string{"KEY-1", "KEY-2"} testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, testAPIEndpoint) decoder := json.NewDecoder(r.Body) diff --git a/onpremise/user.go b/onpremise/user.go index d9f05e7..2cf67b6 100644 --- a/onpremise/user.go +++ b/onpremise/user.go @@ -87,7 +87,7 @@ func (s *UserService) GetByAccountID(ctx context.Context, accountID string) (*Us // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-createUser func (s *UserService) Create(ctx context.Context, user *User) (*User, *Response, error) { apiEndpoint := "/rest/api/2/user" - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, user) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, user) if err != nil { return nil, nil, err } diff --git a/onpremise/user_test.go b/onpremise/user_test.go index 5fd0108..2397f42 100644 --- a/onpremise/user_test.go +++ b/onpremise/user_test.go @@ -57,7 +57,7 @@ func TestUserService_Create(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/user") w.WriteHeader(http.StatusCreated) diff --git a/onpremise/version.go b/onpremise/version.go index 026e1e8..6b12ab9 100644 --- a/onpremise/version.go +++ b/onpremise/version.go @@ -50,7 +50,7 @@ func (s *VersionService) Get(ctx context.Context, versionID int) (*Version, *Res // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-post func (s *VersionService) Create(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := "/rest/api/2/version" - req, err := s.client.NewRequest(ctx, "POST", apiEndpoint, version) + req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, version) if err != nil { return nil, nil, err } diff --git a/onpremise/version_test.go b/onpremise/version_test.go index 0bddae9..459f989 100644 --- a/onpremise/version_test.go +++ b/onpremise/version_test.go @@ -42,7 +42,7 @@ func TestVersionService_Create(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/version", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") + testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/version") w.WriteHeader(http.StatusCreated) From 4d9ce5f4ed2ba72a722c91c0893913d5136c020a Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 21:22:46 +0200 Subject: [PATCH 044/189] Replaced "PUT" with http.MethodPut Related: #508 --- cloud/issue.go | 12 ++++++------ cloud/issue_test.go | 12 ++++++------ cloud/issuelinktype.go | 2 +- cloud/issuelinktype_test.go | 2 +- cloud/organization.go | 2 +- cloud/organization_test.go | 2 +- cloud/version.go | 2 +- cloud/version_test.go | 2 +- onpremise/issue.go | 12 ++++++------ onpremise/issue_test.go | 12 ++++++------ onpremise/issuelinktype.go | 2 +- onpremise/issuelinktype_test.go | 2 +- onpremise/organization.go | 2 +- onpremise/organization_test.go | 2 +- onpremise/version.go | 2 +- onpremise/version_test.go | 2 +- 16 files changed, 36 insertions(+), 36 deletions(-) diff --git a/cloud/issue.go b/cloud/issue.go index 444de79..8d9ca4b 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -815,7 +815,7 @@ func (s *IssueService) UpdateWithOptions(ctx context.Context, issue *Issue, opts if err != nil { return nil, nil, err } - req, err := s.client.NewRequest(ctx, "PUT", url, issue) + req, err := s.client.NewRequest(ctx, http.MethodPut, url, issue) if err != nil { return nil, nil, err } @@ -844,7 +844,7 @@ func (s *IssueService) Update(ctx context.Context, issue *Issue) (*Issue, *Respo // Caller must close resp.Body func (s *IssueService) UpdateIssue(ctx context.Context, jiraID string, data map[string]interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", jiraID) - req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, data) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, data) if err != nil { return nil, err } @@ -888,7 +888,7 @@ func (s *IssueService) UpdateComment(ctx context.Context, issueID string, commen Body: comment.Body, } apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, comment.ID) - req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, reqBody) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, reqBody) if err != nil { return nil, nil, err } @@ -954,7 +954,7 @@ func (s *IssueService) AddWorklogRecord(ctx context.Context, issueID string, rec // https://docs.atlassian.com/software/jira/docs/api/REST/7.1.2/#api/2/issue-updateWorklog func (s *IssueService) UpdateWorklogRecord(ctx context.Context, issueID, worklogID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog/%s", issueID, worklogID) - req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, record) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, record) if err != nil { return nil, nil, err } @@ -1353,7 +1353,7 @@ func (s *IssueService) RemoveWatcher(ctx context.Context, issueID string, userNa func (s *IssueService) UpdateAssignee(ctx context.Context, issueID string, assignee *User) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/assignee", issueID) - req, err := s.client.NewRequest(ctx, "PUT", apiEndPoint, assignee) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndPoint, assignee) if err != nil { return nil, err } @@ -1419,7 +1419,7 @@ func (s *IssueService) AddRemoteLink(ctx context.Context, issueID string, remote // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-remote-links/#api-rest-api-2-issue-issueidorkey-remotelink-linkid-put func (s *IssueService) UpdateRemoteLink(ctx context.Context, issueID string, linkID int, remotelink *RemoteLink) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink/%d", issueID, linkID) - req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, remotelink) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, remotelink) if err != nil { return nil, err } diff --git a/cloud/issue_test.go b/cloud/issue_test.go index 8a4086e..8409fd6 100644 --- a/cloud/issue_test.go +++ b/cloud/issue_test.go @@ -134,7 +134,7 @@ func TestIssueService_Update(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/PROJ-9001", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/issue/PROJ-9001") w.WriteHeader(http.StatusNoContent) @@ -159,7 +159,7 @@ func TestIssueService_UpdateIssue(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/PROJ-9001", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/issue/PROJ-9001") w.WriteHeader(http.StatusNoContent) @@ -209,7 +209,7 @@ func TestIssueService_UpdateComment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/comment/10001", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/issue/10000/comment/10001") w.WriteHeader(http.StatusCreated) @@ -276,7 +276,7 @@ func TestIssueService_UpdateWorklogRecord(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/worklog/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/issue/10000/worklog/1") w.WriteHeader(http.StatusOK) @@ -1669,7 +1669,7 @@ func TestIssueService_UpdateAssignee(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002/assignee", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/issue/10002/assignee") w.WriteHeader(http.StatusNoContent) @@ -1866,7 +1866,7 @@ func TestIssueService_UpdateRemoteLink(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/100/remotelink/200", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/issue/100/remotelink/200") w.WriteHeader(http.StatusNoContent) diff --git a/cloud/issuelinktype.go b/cloud/issuelinktype.go index 704421e..1434576 100644 --- a/cloud/issuelinktype.go +++ b/cloud/issuelinktype.go @@ -85,7 +85,7 @@ func (s *IssueLinkTypeService) Create(ctx context.Context, linkType *IssueLinkTy // Caller must close resp.Body func (s *IssueLinkTypeService) Update(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", linkType.ID) - req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, linkType) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, linkType) if err != nil { return nil, nil, err } diff --git a/cloud/issuelinktype_test.go b/cloud/issuelinktype_test.go index 3b0ad07..36dd2a1 100644 --- a/cloud/issuelinktype_test.go +++ b/cloud/issuelinktype_test.go @@ -79,7 +79,7 @@ func TestIssueLinkTypeService_Update(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issueLinkType/100", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/issueLinkType/100") w.WriteHeader(http.StatusNoContent) diff --git a/cloud/organization.go b/cloud/organization.go index cce347a..616e87c 100644 --- a/cloud/organization.go +++ b/cloud/organization.go @@ -224,7 +224,7 @@ func (s *OrganizationService) GetProperty(ctx context.Context, organizationID in func (s *OrganizationService) SetProperty(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) - req, err := s.client.NewRequest(ctx, "PUT", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { diff --git a/cloud/organization_test.go b/cloud/organization_test.go index 471e507..2c37754 100644 --- a/cloud/organization_test.go +++ b/cloud/organization_test.go @@ -167,7 +167,7 @@ func TestOrganizationService_SetProperty(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1/property/organization.attributes", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/servicedeskapi/organization/1/property/organization.attributes") w.WriteHeader(http.StatusOK) diff --git a/cloud/version.go b/cloud/version.go index 6c79df2..b5780c6 100644 --- a/cloud/version.go +++ b/cloud/version.go @@ -81,7 +81,7 @@ func (s *VersionService) Create(ctx context.Context, version *Version) (*Version // Caller must close resp.Body func (s *VersionService) Update(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/version/%v", version.ID) - req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, version) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, version) if err != nil { return nil, nil, err } diff --git a/cloud/version_test.go b/cloud/version_test.go index 4a1b6c2..b624f1a 100644 --- a/cloud/version_test.go +++ b/cloud/version_test.go @@ -82,7 +82,7 @@ func TestServiceService_Update(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/version/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/version/10002") fmt.Fprint(w, `{ "description": "An excellent updated version", diff --git a/onpremise/issue.go b/onpremise/issue.go index 9b26e9e..58a44d1 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -815,7 +815,7 @@ func (s *IssueService) UpdateWithOptions(ctx context.Context, issue *Issue, opts if err != nil { return nil, nil, err } - req, err := s.client.NewRequest(ctx, "PUT", url, issue) + req, err := s.client.NewRequest(ctx, http.MethodPut, url, issue) if err != nil { return nil, nil, err } @@ -844,7 +844,7 @@ func (s *IssueService) Update(ctx context.Context, issue *Issue) (*Issue, *Respo // Caller must close resp.Body func (s *IssueService) UpdateIssue(ctx context.Context, jiraID string, data map[string]interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", jiraID) - req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, data) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, data) if err != nil { return nil, err } @@ -888,7 +888,7 @@ func (s *IssueService) UpdateComment(ctx context.Context, issueID string, commen Body: comment.Body, } apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, comment.ID) - req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, reqBody) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, reqBody) if err != nil { return nil, nil, err } @@ -954,7 +954,7 @@ func (s *IssueService) AddWorklogRecord(ctx context.Context, issueID string, rec // https://docs.atlassian.com/software/jira/docs/api/REST/7.1.2/#api/2/issue-updateWorklog func (s *IssueService) UpdateWorklogRecord(ctx context.Context, issueID, worklogID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog/%s", issueID, worklogID) - req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, record) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, record) if err != nil { return nil, nil, err } @@ -1353,7 +1353,7 @@ func (s *IssueService) RemoveWatcher(ctx context.Context, issueID string, userNa func (s *IssueService) UpdateAssignee(ctx context.Context, issueID string, assignee *User) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/assignee", issueID) - req, err := s.client.NewRequest(ctx, "PUT", apiEndPoint, assignee) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndPoint, assignee) if err != nil { return nil, err } @@ -1419,7 +1419,7 @@ func (s *IssueService) AddRemoteLink(ctx context.Context, issueID string, remote // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-remote-links/#api-rest-api-2-issue-issueidorkey-remotelink-linkid-put func (s *IssueService) UpdateRemoteLink(ctx context.Context, issueID string, linkID int, remotelink *RemoteLink) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink/%d", issueID, linkID) - req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, remotelink) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, remotelink) if err != nil { return nil, err } diff --git a/onpremise/issue_test.go b/onpremise/issue_test.go index ee58b78..7a4f6bc 100644 --- a/onpremise/issue_test.go +++ b/onpremise/issue_test.go @@ -134,7 +134,7 @@ func TestIssueService_Update(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/PROJ-9001", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/issue/PROJ-9001") w.WriteHeader(http.StatusNoContent) @@ -159,7 +159,7 @@ func TestIssueService_UpdateIssue(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/PROJ-9001", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/issue/PROJ-9001") w.WriteHeader(http.StatusNoContent) @@ -209,7 +209,7 @@ func TestIssueService_UpdateComment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/comment/10001", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/issue/10000/comment/10001") w.WriteHeader(http.StatusCreated) @@ -276,7 +276,7 @@ func TestIssueService_UpdateWorklogRecord(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/worklog/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/issue/10000/worklog/1") w.WriteHeader(http.StatusOK) @@ -1669,7 +1669,7 @@ func TestIssueService_UpdateAssignee(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002/assignee", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/issue/10002/assignee") w.WriteHeader(http.StatusNoContent) @@ -1866,7 +1866,7 @@ func TestIssueService_UpdateRemoteLink(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/100/remotelink/200", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/issue/100/remotelink/200") w.WriteHeader(http.StatusNoContent) diff --git a/onpremise/issuelinktype.go b/onpremise/issuelinktype.go index 8697f26..c852308 100644 --- a/onpremise/issuelinktype.go +++ b/onpremise/issuelinktype.go @@ -85,7 +85,7 @@ func (s *IssueLinkTypeService) Create(ctx context.Context, linkType *IssueLinkTy // Caller must close resp.Body func (s *IssueLinkTypeService) Update(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", linkType.ID) - req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, linkType) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, linkType) if err != nil { return nil, nil, err } diff --git a/onpremise/issuelinktype_test.go b/onpremise/issuelinktype_test.go index 4d20c22..8d6ef59 100644 --- a/onpremise/issuelinktype_test.go +++ b/onpremise/issuelinktype_test.go @@ -79,7 +79,7 @@ func TestIssueLinkTypeService_Update(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issueLinkType/100", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/issueLinkType/100") w.WriteHeader(http.StatusNoContent) diff --git a/onpremise/organization.go b/onpremise/organization.go index 406b48c..b59a4e5 100644 --- a/onpremise/organization.go +++ b/onpremise/organization.go @@ -224,7 +224,7 @@ func (s *OrganizationService) GetProperty(ctx context.Context, organizationID in func (s *OrganizationService) SetProperty(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) - req, err := s.client.NewRequest(ctx, "PUT", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { diff --git a/onpremise/organization_test.go b/onpremise/organization_test.go index 5726f96..a80d5f3 100644 --- a/onpremise/organization_test.go +++ b/onpremise/organization_test.go @@ -167,7 +167,7 @@ func TestOrganizationService_SetProperty(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1/property/organization.attributes", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/servicedeskapi/organization/1/property/organization.attributes") w.WriteHeader(http.StatusOK) diff --git a/onpremise/version.go b/onpremise/version.go index 6b12ab9..697cb51 100644 --- a/onpremise/version.go +++ b/onpremise/version.go @@ -81,7 +81,7 @@ func (s *VersionService) Create(ctx context.Context, version *Version) (*Version // Caller must close resp.Body func (s *VersionService) Update(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/version/%v", version.ID) - req, err := s.client.NewRequest(ctx, "PUT", apiEndpoint, version) + req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, version) if err != nil { return nil, nil, err } diff --git a/onpremise/version_test.go b/onpremise/version_test.go index 459f989..ce731c0 100644 --- a/onpremise/version_test.go +++ b/onpremise/version_test.go @@ -82,7 +82,7 @@ func TestServiceService_Update(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/version/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") + testMethod(t, r, http.MethodPut) testRequestURL(t, r, "/rest/api/2/version/10002") fmt.Fprint(w, `{ "description": "An excellent updated version", From e936c72a17c72e643f1e844e3ac7e58a1203d9b3 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 21:23:44 +0200 Subject: [PATCH 045/189] Replaced "DELETE" with http.MethodDelete Related: #508 --- cloud/authentication.go | 2 +- cloud/authentication_test.go | 4 ++-- cloud/board.go | 2 +- cloud/board_test.go | 2 +- cloud/group.go | 2 +- cloud/group_test.go | 2 +- cloud/issue.go | 10 +++++----- cloud/issue_test.go | 8 ++++---- cloud/issuelinktype.go | 2 +- cloud/issuelinktype_test.go | 2 +- cloud/organization.go | 6 +++--- cloud/organization_test.go | 6 +++--- cloud/servicedesk.go | 4 ++-- cloud/servicedesk_test.go | 6 +++--- cloud/user.go | 2 +- cloud/user_test.go | 2 +- onpremise/authentication.go | 2 +- onpremise/authentication_test.go | 4 ++-- onpremise/board.go | 2 +- onpremise/board_test.go | 2 +- onpremise/group.go | 2 +- onpremise/group_test.go | 2 +- onpremise/issue.go | 10 +++++----- onpremise/issue_test.go | 8 ++++---- onpremise/issuelinktype.go | 2 +- onpremise/issuelinktype_test.go | 2 +- onpremise/organization.go | 6 +++--- onpremise/organization_test.go | 6 +++--- onpremise/servicedesk.go | 4 ++-- onpremise/servicedesk_test.go | 6 +++--- onpremise/user.go | 2 +- onpremise/user_test.go | 2 +- 32 files changed, 62 insertions(+), 62 deletions(-) diff --git a/cloud/authentication.go b/cloud/authentication.go index 166d5fd..20d54bf 100644 --- a/cloud/authentication.go +++ b/cloud/authentication.go @@ -126,7 +126,7 @@ func (s *AuthenticationService) Logout(ctx context.Context) error { } apiEndpoint := "rest/auth/1/session" - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return fmt.Errorf("creating the request to log the user out failed : %s", err) } diff --git a/cloud/authentication_test.go b/cloud/authentication_test.go index 19bd5e5..47cf5e4 100644 --- a/cloud/authentication_test.go +++ b/cloud/authentication_test.go @@ -291,7 +291,7 @@ func TestAuthenticationService_Logout_Success(t *testing.T) { fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) } - if r.Method == "DELETE" { + if r.Method == http.MethodDelete { // return 204 w.WriteHeader(http.StatusNoContent) } @@ -310,7 +310,7 @@ func TestAuthenticationService_Logout_FailWithoutLogin(t *testing.T) { defer teardown() testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == "DELETE" { + if r.Method == http.MethodDelete { // 401 w.WriteHeader(http.StatusUnauthorized) } diff --git a/cloud/board.go b/cloud/board.go index 0893062..37b5167 100644 --- a/cloud/board.go +++ b/cloud/board.go @@ -202,7 +202,7 @@ func (s *BoardService) CreateBoard(ctx context.Context, board *Board) (*Board, * // Caller must close resp.Body func (s *BoardService) DeleteBoard(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/cloud/board_test.go b/cloud/board_test.go index c190256..2495dce 100644 --- a/cloud/board_test.go +++ b/cloud/board_test.go @@ -139,7 +139,7 @@ func TestBoardService_DeleteBoard(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/agile/1.0/board/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/agile/1.0/board/1") w.WriteHeader(http.StatusNoContent) diff --git a/cloud/group.go b/cloud/group.go index 49f02c1..5a3da1c 100644 --- a/cloud/group.go +++ b/cloud/group.go @@ -142,7 +142,7 @@ func (s *GroupService) Add(ctx context.Context, groupname string, username strin // Caller must close resp.Body func (s *GroupService) Remove(ctx context.Context, groupname string, username string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s&username=%s", groupname, username) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return nil, err } diff --git a/cloud/group_test.go b/cloud/group_test.go index c6acefb..43fff23 100644 --- a/cloud/group_test.go +++ b/cloud/group_test.go @@ -99,7 +99,7 @@ func TestGroupService_Remove(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/group/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/api/2/group/user?groupname=default") w.WriteHeader(http.StatusOK) diff --git a/cloud/issue.go b/cloud/issue.go index 8d9ca4b..b459cd0 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -701,7 +701,7 @@ func (s *IssueService) PostAttachment(ctx context.Context, issueID string, r io. func (s *IssueService) DeleteAttachment(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/attachment/%s", attachmentID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return nil, err } @@ -720,7 +720,7 @@ func (s *IssueService) DeleteAttachment(ctx context.Context, attachmentID string func (s *IssueService) DeleteLink(ctx context.Context, linkID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLink/%s", linkID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return nil, err } @@ -907,7 +907,7 @@ func (s *IssueService) UpdateComment(ctx context.Context, issueID string, commen // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-issue-issueIdOrKey-comment-id-delete func (s *IssueService) DeleteComment(ctx context.Context, issueID, commentID string) error { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, commentID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return err } @@ -1265,7 +1265,7 @@ func (s *IssueService) Delete(ctx context.Context, issueID string) (*Response, e deletePayload["deleteSubtasks"] = "true" content, _ := json.Marshal(deletePayload) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, content) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, content) if err != nil { return nil, err } @@ -1333,7 +1333,7 @@ func (s *IssueService) AddWatcher(ctx context.Context, issueID string, userName func (s *IssueService) RemoveWatcher(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, userName) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndPoint, userName) if err != nil { return nil, err } diff --git a/cloud/issue_test.go b/cloud/issue_test.go index 8409fd6..3618b4c 100644 --- a/cloud/issue_test.go +++ b/cloud/issue_test.go @@ -237,7 +237,7 @@ func TestIssueService_DeleteComment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/comment/10001", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/api/2/issue/10000/comment/10001") w.WriteHeader(http.StatusNoContent) @@ -567,7 +567,7 @@ func TestIssueService_DeleteAttachment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/attachment/10054", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/api/2/attachment/10054") w.WriteHeader(http.StatusNoContent) @@ -594,7 +594,7 @@ func TestIssueService_DeleteLink(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issueLink/10054", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/api/2/issueLink/10054") w.WriteHeader(http.StatusNoContent) @@ -1443,7 +1443,7 @@ func TestIssueService_Delete(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/api/2/issue/10002") w.WriteHeader(http.StatusNoContent) diff --git a/cloud/issuelinktype.go b/cloud/issuelinktype.go index 1434576..5664a7b 100644 --- a/cloud/issuelinktype.go +++ b/cloud/issuelinktype.go @@ -103,7 +103,7 @@ func (s *IssueLinkTypeService) Update(ctx context.Context, linkType *IssueLinkTy // Caller must close resp.Body func (s *IssueLinkTypeService) Delete(ctx context.Context, ID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return nil, err } diff --git a/cloud/issuelinktype_test.go b/cloud/issuelinktype_test.go index 36dd2a1..1bcbbc6 100644 --- a/cloud/issuelinktype_test.go +++ b/cloud/issuelinktype_test.go @@ -103,7 +103,7 @@ func TestIssueLinkTypeService_Delete(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issueLinkType/100", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/api/2/issueLinkType/100") w.WriteHeader(http.StatusNoContent) diff --git a/cloud/organization.go b/cloud/organization.go index 616e87c..70db2f2 100644 --- a/cloud/organization.go +++ b/cloud/organization.go @@ -149,7 +149,7 @@ func (s *OrganizationService) GetOrganization(ctx context.Context, organizationI func (s *OrganizationService) DeleteOrganization(ctx context.Context, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndPoint, nil) if err != nil { return nil, err @@ -247,7 +247,7 @@ func (s *OrganizationService) SetProperty(ctx context.Context, organizationID in func (s *OrganizationService) DeleteProperty(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -319,7 +319,7 @@ func (s *OrganizationService) AddUsers(ctx context.Context, organizationID int, func (s *OrganizationService) RemoveUsers(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { diff --git a/cloud/organization_test.go b/cloud/organization_test.go index 2c37754..64c1231 100644 --- a/cloud/organization_test.go +++ b/cloud/organization_test.go @@ -88,7 +88,7 @@ func TestOrganizationService_DeleteOrganization(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/servicedeskapi/organization/1") w.WriteHeader(http.StatusNoContent) @@ -185,7 +185,7 @@ func TestOrganizationService_DeleteProperty(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1/property/organization.attributes", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/servicedeskapi/organization/1/property/organization.attributes") w.WriteHeader(http.StatusOK) @@ -306,7 +306,7 @@ func TestOrganizationService_RemoveUsers(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/servicedeskapi/organization/1/user") w.WriteHeader(http.StatusNoContent) diff --git a/cloud/servicedesk.go b/cloud/servicedesk.go index ef243b3..c370c20 100644 --- a/cloud/servicedesk.go +++ b/cloud/servicedesk.go @@ -88,7 +88,7 @@ func (s *ServiceDeskService) RemoveOrganization(ctx context.Context, serviceDesk OrganizationID: organizationID, } - req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, organization) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndPoint, organization) if err != nil { return nil, err @@ -141,7 +141,7 @@ func (s *ServiceDeskService) RemoveCustomers(ctx context.Context, serviceDeskID }{ AccountIDs: acountIDs, } - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, payload) if err != nil { return nil, err } diff --git a/cloud/servicedesk_test.go b/cloud/servicedesk_test.go index 8a8f683..dc4c3f5 100644 --- a/cloud/servicedesk_test.go +++ b/cloud/servicedesk_test.go @@ -91,7 +91,7 @@ func TestServiceDeskService_RemoveOrganizations(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/servicedesk/10001/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/10001/organization") w.WriteHeader(http.StatusNoContent) @@ -184,7 +184,7 @@ func TestServiceDeskServiceStringServiceDeskID_RemoveOrganizations(t *testing.T) setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/servicedesk/TEST/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/TEST/organization") w.WriteHeader(http.StatusNoContent) @@ -293,7 +293,7 @@ func TestServiceDeskService_RemoveCustomers(t *testing.T) { ) testMux.HandleFunc(fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID), func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID)) var payload struct { diff --git a/cloud/user.go b/cloud/user.go index 4c2691c..cc397cc 100644 --- a/cloud/user.go +++ b/cloud/user.go @@ -119,7 +119,7 @@ func (s *UserService) Create(ctx context.Context, user *User) (*User, *Response, // Caller must close resp.Body func (s *UserService) Delete(ctx context.Context, accountId string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return nil, err } diff --git a/cloud/user_test.go b/cloud/user_test.go index 0f18db9..192688e 100644 --- a/cloud/user_test.go +++ b/cloud/user_test.go @@ -84,7 +84,7 @@ func TestUserService_Delete(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") w.WriteHeader(http.StatusNoContent) diff --git a/onpremise/authentication.go b/onpremise/authentication.go index be5d402..a08cdb8 100644 --- a/onpremise/authentication.go +++ b/onpremise/authentication.go @@ -126,7 +126,7 @@ func (s *AuthenticationService) Logout(ctx context.Context) error { } apiEndpoint := "rest/auth/1/session" - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return fmt.Errorf("creating the request to log the user out failed : %s", err) } diff --git a/onpremise/authentication_test.go b/onpremise/authentication_test.go index 295de5a..b9299e4 100644 --- a/onpremise/authentication_test.go +++ b/onpremise/authentication_test.go @@ -291,7 +291,7 @@ func TestAuthenticationService_Logout_Success(t *testing.T) { fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) } - if r.Method == "DELETE" { + if r.Method == http.MethodDelete { // return 204 w.WriteHeader(http.StatusNoContent) } @@ -310,7 +310,7 @@ func TestAuthenticationService_Logout_FailWithoutLogin(t *testing.T) { defer teardown() testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == "DELETE" { + if r.Method == http.MethodDelete { // 401 w.WriteHeader(http.StatusUnauthorized) } diff --git a/onpremise/board.go b/onpremise/board.go index 7326b8c..1404520 100644 --- a/onpremise/board.go +++ b/onpremise/board.go @@ -202,7 +202,7 @@ func (s *BoardService) CreateBoard(ctx context.Context, board *Board) (*Board, * // Caller must close resp.Body func (s *BoardService) DeleteBoard(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return nil, nil, err } diff --git a/onpremise/board_test.go b/onpremise/board_test.go index baf575d..3ec06aa 100644 --- a/onpremise/board_test.go +++ b/onpremise/board_test.go @@ -139,7 +139,7 @@ func TestBoardService_DeleteBoard(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/agile/1.0/board/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/agile/1.0/board/1") w.WriteHeader(http.StatusNoContent) diff --git a/onpremise/group.go b/onpremise/group.go index ff2a73e..44074ee 100644 --- a/onpremise/group.go +++ b/onpremise/group.go @@ -142,7 +142,7 @@ func (s *GroupService) Add(ctx context.Context, groupname string, username strin // Caller must close resp.Body func (s *GroupService) Remove(ctx context.Context, groupname string, username string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s&username=%s", groupname, username) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return nil, err } diff --git a/onpremise/group_test.go b/onpremise/group_test.go index a963908..125db70 100644 --- a/onpremise/group_test.go +++ b/onpremise/group_test.go @@ -99,7 +99,7 @@ func TestGroupService_Remove(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/group/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/api/2/group/user?groupname=default") w.WriteHeader(http.StatusOK) diff --git a/onpremise/issue.go b/onpremise/issue.go index 58a44d1..45aade5 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -701,7 +701,7 @@ func (s *IssueService) PostAttachment(ctx context.Context, issueID string, r io. func (s *IssueService) DeleteAttachment(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/attachment/%s", attachmentID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return nil, err } @@ -720,7 +720,7 @@ func (s *IssueService) DeleteAttachment(ctx context.Context, attachmentID string func (s *IssueService) DeleteLink(ctx context.Context, linkID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLink/%s", linkID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return nil, err } @@ -907,7 +907,7 @@ func (s *IssueService) UpdateComment(ctx context.Context, issueID string, commen // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-issue-issueIdOrKey-comment-id-delete func (s *IssueService) DeleteComment(ctx context.Context, issueID, commentID string) error { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, commentID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return err } @@ -1265,7 +1265,7 @@ func (s *IssueService) Delete(ctx context.Context, issueID string) (*Response, e deletePayload["deleteSubtasks"] = "true" content, _ := json.Marshal(deletePayload) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, content) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, content) if err != nil { return nil, err } @@ -1333,7 +1333,7 @@ func (s *IssueService) AddWatcher(ctx context.Context, issueID string, userName func (s *IssueService) RemoveWatcher(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, userName) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndPoint, userName) if err != nil { return nil, err } diff --git a/onpremise/issue_test.go b/onpremise/issue_test.go index 7a4f6bc..36186f7 100644 --- a/onpremise/issue_test.go +++ b/onpremise/issue_test.go @@ -237,7 +237,7 @@ func TestIssueService_DeleteComment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/comment/10001", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/api/2/issue/10000/comment/10001") w.WriteHeader(http.StatusNoContent) @@ -567,7 +567,7 @@ func TestIssueService_DeleteAttachment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/attachment/10054", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/api/2/attachment/10054") w.WriteHeader(http.StatusNoContent) @@ -594,7 +594,7 @@ func TestIssueService_DeleteLink(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issueLink/10054", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/api/2/issueLink/10054") w.WriteHeader(http.StatusNoContent) @@ -1443,7 +1443,7 @@ func TestIssueService_Delete(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/api/2/issue/10002") w.WriteHeader(http.StatusNoContent) diff --git a/onpremise/issuelinktype.go b/onpremise/issuelinktype.go index c852308..5599084 100644 --- a/onpremise/issuelinktype.go +++ b/onpremise/issuelinktype.go @@ -103,7 +103,7 @@ func (s *IssueLinkTypeService) Update(ctx context.Context, linkType *IssueLinkTy // Caller must close resp.Body func (s *IssueLinkTypeService) Delete(ctx context.Context, ID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return nil, err } diff --git a/onpremise/issuelinktype_test.go b/onpremise/issuelinktype_test.go index 8d6ef59..374bebc 100644 --- a/onpremise/issuelinktype_test.go +++ b/onpremise/issuelinktype_test.go @@ -103,7 +103,7 @@ func TestIssueLinkTypeService_Delete(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issueLinkType/100", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/api/2/issueLinkType/100") w.WriteHeader(http.StatusNoContent) diff --git a/onpremise/organization.go b/onpremise/organization.go index b59a4e5..c75ba9d 100644 --- a/onpremise/organization.go +++ b/onpremise/organization.go @@ -149,7 +149,7 @@ func (s *OrganizationService) GetOrganization(ctx context.Context, organizationI func (s *OrganizationService) DeleteOrganization(ctx context.Context, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndPoint, nil) if err != nil { return nil, err @@ -247,7 +247,7 @@ func (s *OrganizationService) SetProperty(ctx context.Context, organizationID in func (s *OrganizationService) DeleteProperty(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { @@ -319,7 +319,7 @@ func (s *OrganizationService) AddUsers(ctx context.Context, organizationID int, func (s *OrganizationService) RemoveUsers(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndPoint, nil) req.Header.Set("Accept", "application/json") if err != nil { diff --git a/onpremise/organization_test.go b/onpremise/organization_test.go index a80d5f3..a4e5826 100644 --- a/onpremise/organization_test.go +++ b/onpremise/organization_test.go @@ -88,7 +88,7 @@ func TestOrganizationService_DeleteOrganization(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/servicedeskapi/organization/1") w.WriteHeader(http.StatusNoContent) @@ -185,7 +185,7 @@ func TestOrganizationService_DeleteProperty(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1/property/organization.attributes", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/servicedeskapi/organization/1/property/organization.attributes") w.WriteHeader(http.StatusOK) @@ -306,7 +306,7 @@ func TestOrganizationService_RemoveUsers(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/organization/1/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/servicedeskapi/organization/1/user") w.WriteHeader(http.StatusNoContent) diff --git a/onpremise/servicedesk.go b/onpremise/servicedesk.go index 2f4844e..f3da1d9 100644 --- a/onpremise/servicedesk.go +++ b/onpremise/servicedesk.go @@ -88,7 +88,7 @@ func (s *ServiceDeskService) RemoveOrganization(ctx context.Context, serviceDesk OrganizationID: organizationID, } - req, err := s.client.NewRequest(ctx, "DELETE", apiEndPoint, organization) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndPoint, organization) if err != nil { return nil, err @@ -141,7 +141,7 @@ func (s *ServiceDeskService) RemoveCustomers(ctx context.Context, serviceDeskID }{ AccountIDs: acountIDs, } - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, payload) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, payload) if err != nil { return nil, err } diff --git a/onpremise/servicedesk_test.go b/onpremise/servicedesk_test.go index 34002aa..d1c3df4 100644 --- a/onpremise/servicedesk_test.go +++ b/onpremise/servicedesk_test.go @@ -91,7 +91,7 @@ func TestServiceDeskService_RemoveOrganizations(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/servicedesk/10001/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/10001/organization") w.WriteHeader(http.StatusNoContent) @@ -184,7 +184,7 @@ func TestServiceDeskServiceStringServiceDeskID_RemoveOrganizations(t *testing.T) setup() defer teardown() testMux.HandleFunc("/rest/servicedeskapi/servicedesk/TEST/organization", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/servicedeskapi/servicedesk/TEST/organization") w.WriteHeader(http.StatusNoContent) @@ -293,7 +293,7 @@ func TestServiceDeskService_RemoveCustomers(t *testing.T) { ) testMux.HandleFunc(fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID), func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, fmt.Sprintf("/rest/servicedeskapi/servicedesk/%v/customer", test.serviceDeskID)) var payload struct { diff --git a/onpremise/user.go b/onpremise/user.go index 2cf67b6..a2a8c7f 100644 --- a/onpremise/user.go +++ b/onpremise/user.go @@ -119,7 +119,7 @@ func (s *UserService) Create(ctx context.Context, user *User) (*User, *Response, // Caller must close resp.Body func (s *UserService) Delete(ctx context.Context, accountId string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) - req, err := s.client.NewRequest(ctx, "DELETE", apiEndpoint, nil) + req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return nil, err } diff --git a/onpremise/user_test.go b/onpremise/user_test.go index 2397f42..273bc3a 100644 --- a/onpremise/user_test.go +++ b/onpremise/user_test.go @@ -84,7 +84,7 @@ func TestUserService_Delete(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") + testMethod(t, r, http.MethodDelete) testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000") w.WriteHeader(http.StatusNoContent) From efd864b5826c14453d6ccefba538dd6dc070de27 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 21:25:23 +0200 Subject: [PATCH 046/189] Add Changelog for "Replace all "GET", "POST", ... with http.MethodGet (and related) constants" --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 134755b..ba003de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -179,6 +179,9 @@ client.Issue.Create(ctx, ...) * UserAgent: Client HTTP calls are now identifable via a User Agent. This user agent can be configured (default: `go-jira/2.0.0`) * The underlying used HTTP client for API calls can be retrieved via `client.Client()` +### Other + +* Replace all "GET", "POST", ... with http.MethodGet (and related) constants ### Changes From f2d52eb5999d1608c6c6686b69856bcfbf9a9ce0 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 21:37:28 +0200 Subject: [PATCH 047/189] Get v2 working with go modules Fix #482 --- cloud/examples/addlabel/main.go | 2 +- cloud/examples/basicauth/main.go | 2 +- cloud/examples/create/main.go | 2 +- cloud/examples/createwithcustomfields/main.go | 2 +- cloud/examples/do/main.go | 2 +- cloud/examples/ignorecerts/main.go | 2 +- cloud/examples/jql/main.go | 2 +- cloud/examples/newclient/main.go | 2 +- cloud/examples/pagination/main.go | 2 +- cloud/examples/renderedfields/main.go | 2 +- cloud/examples/searchpages/main.go | 2 +- go.mod | 2 +- onpremise/examples/addlabel/main.go | 2 +- onpremise/examples/basicauth/main.go | 2 +- onpremise/examples/create/main.go | 2 +- onpremise/examples/createwithcustomfields/main.go | 2 +- onpremise/examples/do/main.go | 2 +- onpremise/examples/ignorecerts/main.go | 2 +- onpremise/examples/jql/main.go | 2 +- onpremise/examples/newclient/main.go | 2 +- onpremise/examples/pagination/main.go | 2 +- onpremise/examples/renderedfields/main.go | 2 +- onpremise/examples/searchpages/main.go | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/cloud/examples/addlabel/main.go b/cloud/examples/addlabel/main.go index 12adcb1..2499c1c 100644 --- a/cloud/examples/addlabel/main.go +++ b/cloud/examples/addlabel/main.go @@ -9,7 +9,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/cloud/v2" "golang.org/x/term" ) diff --git a/cloud/examples/basicauth/main.go b/cloud/examples/basicauth/main.go index 7aa2116..4683800 100644 --- a/cloud/examples/basicauth/main.go +++ b/cloud/examples/basicauth/main.go @@ -10,7 +10,7 @@ import ( "golang.org/x/term" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/cloud/v2" ) func main() { diff --git a/cloud/examples/create/main.go b/cloud/examples/create/main.go index 2734e6c..f398345 100644 --- a/cloud/examples/create/main.go +++ b/cloud/examples/create/main.go @@ -8,7 +8,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/cloud/v2" "golang.org/x/term" ) diff --git a/cloud/examples/createwithcustomfields/main.go b/cloud/examples/createwithcustomfields/main.go index 0f597f2..f849c8a 100644 --- a/cloud/examples/createwithcustomfields/main.go +++ b/cloud/examples/createwithcustomfields/main.go @@ -8,7 +8,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/cloud/v2" "github.com/trivago/tgo/tcontainer" "golang.org/x/term" ) diff --git a/cloud/examples/do/main.go b/cloud/examples/do/main.go index a69b435..82d6cfa 100644 --- a/cloud/examples/do/main.go +++ b/cloud/examples/do/main.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/cloud/v2" ) func main() { diff --git a/cloud/examples/ignorecerts/main.go b/cloud/examples/ignorecerts/main.go index 078a489..de92967 100644 --- a/cloud/examples/ignorecerts/main.go +++ b/cloud/examples/ignorecerts/main.go @@ -6,7 +6,7 @@ import ( "fmt" "net/http" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/cloud/v2" ) func main() { diff --git a/cloud/examples/jql/main.go b/cloud/examples/jql/main.go index b5161b9..1ea045c 100644 --- a/cloud/examples/jql/main.go +++ b/cloud/examples/jql/main.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/cloud/v2" ) func main() { diff --git a/cloud/examples/newclient/main.go b/cloud/examples/newclient/main.go index dea1ecf..ba7841f 100644 --- a/cloud/examples/newclient/main.go +++ b/cloud/examples/newclient/main.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/cloud/v2" ) func main() { diff --git a/cloud/examples/pagination/main.go b/cloud/examples/pagination/main.go index 9a2d2bd..3220b31 100644 --- a/cloud/examples/pagination/main.go +++ b/cloud/examples/pagination/main.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/cloud/v2" ) // GetAllIssues will implement pagination of api and get all the issues. diff --git a/cloud/examples/renderedfields/main.go b/cloud/examples/renderedfields/main.go index 8671120..6afdede 100644 --- a/cloud/examples/renderedfields/main.go +++ b/cloud/examples/renderedfields/main.go @@ -11,7 +11,7 @@ import ( "golang.org/x/term" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/cloud/v2" ) func main() { diff --git a/cloud/examples/searchpages/main.go b/cloud/examples/searchpages/main.go index c98f585..dd4c245 100644 --- a/cloud/examples/searchpages/main.go +++ b/cloud/examples/searchpages/main.go @@ -10,7 +10,7 @@ import ( "syscall" "time" - jira "github.com/andygrunwald/go-jira/cloud" + jira "github.com/andygrunwald/go-jira/cloud/v2" "golang.org/x/term" ) diff --git a/go.mod b/go.mod index bf4eca0..b76d7e6 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/andygrunwald/go-jira +module github.com/andygrunwald/go-jira/v2 go 1.18 diff --git a/onpremise/examples/addlabel/main.go b/onpremise/examples/addlabel/main.go index 94e0673..986f9c7 100644 --- a/onpremise/examples/addlabel/main.go +++ b/onpremise/examples/addlabel/main.go @@ -9,7 +9,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/onpremise/v2" "golang.org/x/term" ) diff --git a/onpremise/examples/basicauth/main.go b/onpremise/examples/basicauth/main.go index 127c55e..bd8b0f3 100644 --- a/onpremise/examples/basicauth/main.go +++ b/onpremise/examples/basicauth/main.go @@ -10,7 +10,7 @@ import ( "golang.org/x/term" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/onpremise/v2" ) func main() { diff --git a/onpremise/examples/create/main.go b/onpremise/examples/create/main.go index 8134703..cea1db5 100644 --- a/onpremise/examples/create/main.go +++ b/onpremise/examples/create/main.go @@ -8,7 +8,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/onpremise/v2" "golang.org/x/term" ) diff --git a/onpremise/examples/createwithcustomfields/main.go b/onpremise/examples/createwithcustomfields/main.go index 71cf096..e3d185e 100644 --- a/onpremise/examples/createwithcustomfields/main.go +++ b/onpremise/examples/createwithcustomfields/main.go @@ -8,7 +8,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/onpremise/v2" "github.com/trivago/tgo/tcontainer" "golang.org/x/term" ) diff --git a/onpremise/examples/do/main.go b/onpremise/examples/do/main.go index 8f1a1c7..209c5d0 100644 --- a/onpremise/examples/do/main.go +++ b/onpremise/examples/do/main.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/onpremise/v2" ) func main() { diff --git a/onpremise/examples/ignorecerts/main.go b/onpremise/examples/ignorecerts/main.go index 037603a..fc61305 100644 --- a/onpremise/examples/ignorecerts/main.go +++ b/onpremise/examples/ignorecerts/main.go @@ -6,7 +6,7 @@ import ( "fmt" "net/http" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/onpremise/v2" ) func main() { diff --git a/onpremise/examples/jql/main.go b/onpremise/examples/jql/main.go index 3c619dc..72b942a 100644 --- a/onpremise/examples/jql/main.go +++ b/onpremise/examples/jql/main.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/onpremise/v2" ) func main() { diff --git a/onpremise/examples/newclient/main.go b/onpremise/examples/newclient/main.go index 96b1aa6..e6d78c8 100644 --- a/onpremise/examples/newclient/main.go +++ b/onpremise/examples/newclient/main.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/onpremise/v2" ) func main() { diff --git a/onpremise/examples/pagination/main.go b/onpremise/examples/pagination/main.go index e8ae219..71eaf23 100644 --- a/onpremise/examples/pagination/main.go +++ b/onpremise/examples/pagination/main.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/onpremise/v2" ) // GetAllIssues will implement pagination of api and get all the issues. diff --git a/onpremise/examples/renderedfields/main.go b/onpremise/examples/renderedfields/main.go index e9e8118..461061a 100644 --- a/onpremise/examples/renderedfields/main.go +++ b/onpremise/examples/renderedfields/main.go @@ -11,7 +11,7 @@ import ( "golang.org/x/term" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/onpremise/v2" ) func main() { diff --git a/onpremise/examples/searchpages/main.go b/onpremise/examples/searchpages/main.go index d525512..1be3307 100644 --- a/onpremise/examples/searchpages/main.go +++ b/onpremise/examples/searchpages/main.go @@ -10,7 +10,7 @@ import ( "syscall" "time" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/onpremise/v2" "golang.org/x/term" ) From 247ff14e8febe57cd0e42f5bdd86483d16644599 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 21:44:56 +0200 Subject: [PATCH 048/189] Fix #482: Fix import statements of examples --- cloud/examples/addlabel/main.go | 2 +- cloud/examples/basicauth/main.go | 2 +- cloud/examples/create/main.go | 2 +- cloud/examples/createwithcustomfields/main.go | 2 +- cloud/examples/do/main.go | 2 +- cloud/examples/ignorecerts/main.go | 2 +- cloud/examples/jql/main.go | 2 +- cloud/examples/newclient/main.go | 2 +- cloud/examples/pagination/main.go | 2 +- cloud/examples/renderedfields/main.go | 2 +- cloud/examples/searchpages/main.go | 2 +- onpremise/examples/addlabel/main.go | 2 +- onpremise/examples/basicauth/main.go | 2 +- onpremise/examples/create/main.go | 2 +- onpremise/examples/createwithcustomfields/main.go | 2 +- onpremise/examples/do/main.go | 2 +- onpremise/examples/ignorecerts/main.go | 2 +- onpremise/examples/jql/main.go | 2 +- onpremise/examples/newclient/main.go | 2 +- onpremise/examples/pagination/main.go | 2 +- onpremise/examples/renderedfields/main.go | 2 +- onpremise/examples/searchpages/main.go | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/cloud/examples/addlabel/main.go b/cloud/examples/addlabel/main.go index 2499c1c..4ece27f 100644 --- a/cloud/examples/addlabel/main.go +++ b/cloud/examples/addlabel/main.go @@ -9,7 +9,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/cloud/v2" + jira "github.com/andygrunwald/go-jira/v2/cloud" "golang.org/x/term" ) diff --git a/cloud/examples/basicauth/main.go b/cloud/examples/basicauth/main.go index 4683800..b04888a 100644 --- a/cloud/examples/basicauth/main.go +++ b/cloud/examples/basicauth/main.go @@ -10,7 +10,7 @@ import ( "golang.org/x/term" - jira "github.com/andygrunwald/go-jira/cloud/v2" + jira "github.com/andygrunwald/go-jira/v2/cloud" ) func main() { diff --git a/cloud/examples/create/main.go b/cloud/examples/create/main.go index f398345..ecfdd7c 100644 --- a/cloud/examples/create/main.go +++ b/cloud/examples/create/main.go @@ -8,7 +8,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/cloud/v2" + jira "github.com/andygrunwald/go-jira/v2/cloud" "golang.org/x/term" ) diff --git a/cloud/examples/createwithcustomfields/main.go b/cloud/examples/createwithcustomfields/main.go index f849c8a..22f531f 100644 --- a/cloud/examples/createwithcustomfields/main.go +++ b/cloud/examples/createwithcustomfields/main.go @@ -8,7 +8,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/cloud/v2" + jira "github.com/andygrunwald/go-jira/v2/cloud" "github.com/trivago/tgo/tcontainer" "golang.org/x/term" ) diff --git a/cloud/examples/do/main.go b/cloud/examples/do/main.go index 82d6cfa..161b6d8 100644 --- a/cloud/examples/do/main.go +++ b/cloud/examples/do/main.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - jira "github.com/andygrunwald/go-jira/cloud/v2" + jira "github.com/andygrunwald/go-jira/v2/cloud" ) func main() { diff --git a/cloud/examples/ignorecerts/main.go b/cloud/examples/ignorecerts/main.go index de92967..db7b774 100644 --- a/cloud/examples/ignorecerts/main.go +++ b/cloud/examples/ignorecerts/main.go @@ -6,7 +6,7 @@ import ( "fmt" "net/http" - jira "github.com/andygrunwald/go-jira/cloud/v2" + jira "github.com/andygrunwald/go-jira/v2/cloud" ) func main() { diff --git a/cloud/examples/jql/main.go b/cloud/examples/jql/main.go index 1ea045c..c224eaf 100644 --- a/cloud/examples/jql/main.go +++ b/cloud/examples/jql/main.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - jira "github.com/andygrunwald/go-jira/cloud/v2" + jira "github.com/andygrunwald/go-jira/v2/cloud" ) func main() { diff --git a/cloud/examples/newclient/main.go b/cloud/examples/newclient/main.go index ba7841f..d1895eb 100644 --- a/cloud/examples/newclient/main.go +++ b/cloud/examples/newclient/main.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - jira "github.com/andygrunwald/go-jira/cloud/v2" + jira "github.com/andygrunwald/go-jira/v2/cloud" ) func main() { diff --git a/cloud/examples/pagination/main.go b/cloud/examples/pagination/main.go index 3220b31..15995e3 100644 --- a/cloud/examples/pagination/main.go +++ b/cloud/examples/pagination/main.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - jira "github.com/andygrunwald/go-jira/cloud/v2" + jira "github.com/andygrunwald/go-jira/v2/cloud" ) // GetAllIssues will implement pagination of api and get all the issues. diff --git a/cloud/examples/renderedfields/main.go b/cloud/examples/renderedfields/main.go index 6afdede..1fc5de5 100644 --- a/cloud/examples/renderedfields/main.go +++ b/cloud/examples/renderedfields/main.go @@ -11,7 +11,7 @@ import ( "golang.org/x/term" - jira "github.com/andygrunwald/go-jira/cloud/v2" + jira "github.com/andygrunwald/go-jira/v2/cloud" ) func main() { diff --git a/cloud/examples/searchpages/main.go b/cloud/examples/searchpages/main.go index dd4c245..56ebebb 100644 --- a/cloud/examples/searchpages/main.go +++ b/cloud/examples/searchpages/main.go @@ -10,7 +10,7 @@ import ( "syscall" "time" - jira "github.com/andygrunwald/go-jira/cloud/v2" + jira "github.com/andygrunwald/go-jira/v2/cloud" "golang.org/x/term" ) diff --git a/onpremise/examples/addlabel/main.go b/onpremise/examples/addlabel/main.go index 986f9c7..0d383ab 100644 --- a/onpremise/examples/addlabel/main.go +++ b/onpremise/examples/addlabel/main.go @@ -9,7 +9,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/onpremise/v2" + jira "github.com/andygrunwald/go-jira/v2/onpremise" "golang.org/x/term" ) diff --git a/onpremise/examples/basicauth/main.go b/onpremise/examples/basicauth/main.go index bd8b0f3..a728e44 100644 --- a/onpremise/examples/basicauth/main.go +++ b/onpremise/examples/basicauth/main.go @@ -10,7 +10,7 @@ import ( "golang.org/x/term" - jira "github.com/andygrunwald/go-jira/onpremise/v2" + jira "github.com/andygrunwald/go-jira/v2/onpremise" ) func main() { diff --git a/onpremise/examples/create/main.go b/onpremise/examples/create/main.go index cea1db5..b99b7c8 100644 --- a/onpremise/examples/create/main.go +++ b/onpremise/examples/create/main.go @@ -8,7 +8,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/onpremise/v2" + jira "github.com/andygrunwald/go-jira/v2/onpremise" "golang.org/x/term" ) diff --git a/onpremise/examples/createwithcustomfields/main.go b/onpremise/examples/createwithcustomfields/main.go index e3d185e..2eb954c 100644 --- a/onpremise/examples/createwithcustomfields/main.go +++ b/onpremise/examples/createwithcustomfields/main.go @@ -8,7 +8,7 @@ import ( "strings" "syscall" - jira "github.com/andygrunwald/go-jira/onpremise/v2" + jira "github.com/andygrunwald/go-jira/v2/onpremise" "github.com/trivago/tgo/tcontainer" "golang.org/x/term" ) diff --git a/onpremise/examples/do/main.go b/onpremise/examples/do/main.go index 209c5d0..7c54e27 100644 --- a/onpremise/examples/do/main.go +++ b/onpremise/examples/do/main.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - jira "github.com/andygrunwald/go-jira/onpremise/v2" + jira "github.com/andygrunwald/go-jira/v2/onpremise" ) func main() { diff --git a/onpremise/examples/ignorecerts/main.go b/onpremise/examples/ignorecerts/main.go index fc61305..a031e0f 100644 --- a/onpremise/examples/ignorecerts/main.go +++ b/onpremise/examples/ignorecerts/main.go @@ -6,7 +6,7 @@ import ( "fmt" "net/http" - jira "github.com/andygrunwald/go-jira/onpremise/v2" + jira "github.com/andygrunwald/go-jira/v2/onpremise" ) func main() { diff --git a/onpremise/examples/jql/main.go b/onpremise/examples/jql/main.go index 72b942a..2509baf 100644 --- a/onpremise/examples/jql/main.go +++ b/onpremise/examples/jql/main.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - jira "github.com/andygrunwald/go-jira/onpremise/v2" + jira "github.com/andygrunwald/go-jira/v2/onpremise" ) func main() { diff --git a/onpremise/examples/newclient/main.go b/onpremise/examples/newclient/main.go index e6d78c8..3565501 100644 --- a/onpremise/examples/newclient/main.go +++ b/onpremise/examples/newclient/main.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - jira "github.com/andygrunwald/go-jira/onpremise/v2" + jira "github.com/andygrunwald/go-jira/v2/onpremise" ) func main() { diff --git a/onpremise/examples/pagination/main.go b/onpremise/examples/pagination/main.go index 71eaf23..b4a8b74 100644 --- a/onpremise/examples/pagination/main.go +++ b/onpremise/examples/pagination/main.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - jira "github.com/andygrunwald/go-jira/onpremise/v2" + jira "github.com/andygrunwald/go-jira/v2/onpremise" ) // GetAllIssues will implement pagination of api and get all the issues. diff --git a/onpremise/examples/renderedfields/main.go b/onpremise/examples/renderedfields/main.go index 461061a..9805902 100644 --- a/onpremise/examples/renderedfields/main.go +++ b/onpremise/examples/renderedfields/main.go @@ -11,7 +11,7 @@ import ( "golang.org/x/term" - jira "github.com/andygrunwald/go-jira/onpremise/v2" + jira "github.com/andygrunwald/go-jira/v2/onpremise" ) func main() { diff --git a/onpremise/examples/searchpages/main.go b/onpremise/examples/searchpages/main.go index 1be3307..0e12a66 100644 --- a/onpremise/examples/searchpages/main.go +++ b/onpremise/examples/searchpages/main.go @@ -10,7 +10,7 @@ import ( "syscall" "time" - jira "github.com/andygrunwald/go-jira/onpremise/v2" + jira "github.com/andygrunwald/go-jira/v2/onpremise" "golang.org/x/term" ) From f0e3592f7ea7d47ff4c921c2ee71c8e5723bf8f1 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 22:27:45 +0200 Subject: [PATCH 049/189] Fix #511: Easier and more lean issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 35 ++++++----------------- .github/ISSUE_TEMPLATE/feature_request.md | 16 +++-------- 2 files changed, 12 insertions(+), 39 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 4228558..99586f8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,28 +7,20 @@ assignees: '' --- -## Describe the bug +## What happened? -A clear and concise description of what the bug is. +Please provide as much info as possible. +Not doing so may result in your bug not being addressed in a timely manner. -Formatting tips: GitHub supports Markdown: https://guides.github.com/features/mastering-markdown/ +## What did you expect to happen? -## To Reproduce -Provide a link to a live example, or an unambiguous set of steps to reproduce this bug. Include configuration, logs, etc. to reproduce, if relevant. +## How can we reproduce it (as minimally and precisely as possible)? -1. -2. -3. -4. +Minimal code helps us to identify the problem faster. -## Expected behavior +## Anything else we need to know? -A clear and concise description of what you expected to happen. - -## Possible Solution - -Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement: the addition or change. ## Your Environment @@ -36,16 +28,5 @@ Include as many relevant details about the environment you experienced the probl * go-jira version (git tag or sha): * Go version (`go version`): -* Jira version: * Jira type (cloud or on-premise): -* Involved Jira plugins: -* Operating System and version: - -## Additional context - -Add any other context about the problem here. - -How has this issue affected you? What are you trying to accomplish? -Providing context helps us come up with a solution that is most useful in the real world. - - +* Jira version / Api version: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index edc78bb..16e8fd7 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,24 +1,16 @@ --- name: Feature request -about: Suggest an idea for this project +about: Suggest a feature for this project title: '' labels: '' assignees: '' --- -## Is your feature request related to a problem? Please describe. +## What would you like to be added? -A clear and concise description of what the problem is. Ex. I'm always using this feature but am missing [...] -## Describe the solution you'd like +## Why is this needed? -A clear and concise description of what you want to happen. -## Describe alternatives you've considered - -A clear and concise description of any alternative solutions or features you've considered. - -## Additional context - -Add any other context or screenshots about the feature request here. \ No newline at end of file +## Anything else we need to know? \ No newline at end of file From d9ec3992684d37c445009142ce75b88b6c85eadf Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 22:31:37 +0200 Subject: [PATCH 050/189] Fix #512: Easier Pull Request template --- .github/PULL_REQUEST_TEMPLATE.md | 41 +++++++++++++------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4a3bf0d..f18673d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,32 +1,25 @@ -# Description +#### What type of PR is this? -Please describe _what does this Pull Request fix or add?_. + -## Example: +#### What this PR does / why we need it: -Let us know how users can use or test this functionality. -```go -// Example code +#### Which issue(s) this PR fixes: -``` +Fixes # -# Checklist +#### Special notes for your reviewer: -* [ ] Unit or Integration tests added - * [ ] Good Path - * [ ] Error Path -* [ ] Commits follow conventions described here: - * [ ] [Conventional Commits 1.0.0](https://conventionalcommits.org/en/v1.0.0-beta.4/#summary) - * [ ] [The seven rules of a great Git commit message](https://chris.beams.io/posts/git-commit/#seven-rules) -* [ ] Commits are squashed such that - * [ ] There is 1 commit per isolated change -* [ ] I've not made extraneous commits/changes that are unrelated to my change. + +#### Additional documentation e.g., usage docs, etc.: From 73fd9019783c4a33cb34d2d4c68c3f3cba4bbd05 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 11 Sep 2022 22:47:08 +0200 Subject: [PATCH 051/189] Basic setup of documentation with material for mkdocs --- .github/workflows/documentation.yml | 16 ++++++++++++++++ docs/index.md | 17 +++++++++++++++++ mkdocs.yml | 19 +++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 .github/workflows/documentation.yml create mode 100644 docs/index.md create mode 100644 mkdocs.yml diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..460fa5b --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,16 @@ +name: Documentation +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: 3.x + - run: pip install mkdocs-material + - run: mkdocs gh-deploy --force \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..000ea34 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,17 @@ +# Welcome to MkDocs + +For full documentation visit [mkdocs.org](https://www.mkdocs.org). + +## Commands + +* `mkdocs new [dir-name]` - Create a new project. +* `mkdocs serve` - Start the live-reloading docs server. +* `mkdocs build` - Build the documentation site. +* `mkdocs -h` - Print help message and exit. + +## Project layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + ... # Other markdown pages, images and other files. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..61df269 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,19 @@ +site_name: go-jira - Go client library for Atlassian Jira +repo_url: https://github.com/andygrunwald/go-jira +theme: + name: material + language: en + +plugins: + - search + +extra: +# version: +# provider: mike + social: + - icon: fontawesome/brands/twitter + link: https://twitter.com/andygrunwald + name: Andy Grunwald on Twitter + - icon: fontawesome/brands/github + link: https://github.com/andygrunwald/go-jira + \ No newline at end of file From 33e2fcb3627710e85fb008dc6ef5023f0d8fac30 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Mon, 12 Sep 2022 20:01:28 +0200 Subject: [PATCH 052/189] BoardService: `BoardService.GetAllSprints` removed, `BoardService.GetAllSprintsWithOptions` renamed Related #294 --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ cloud/board.go | 25 +++---------------------- cloud/board_test.go | 34 +--------------------------------- onpremise/board.go | 25 +++---------------------- onpremise/board_test.go | 34 +--------------------------------- 5 files changed, 43 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba003de..a8f5c9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -166,6 +166,40 @@ the new call would be client.Issue.Create(ctx, ...) ``` +#### `BoardService.GetAllSprints` removed, `BoardService.GetAllSprintsWithOptions` renamed + + +The function `client.BoardService.GetAllSprints()` has been removed. +The function `client.BoardService.GetAllSprintsWithOptions()` has been renamed to `client.BoardService.GetAllSprints()`. + +##### If you used `client.BoardService.GetAllSprints()`: + +Before: + +```go +client.Board.GetAllSprints(context.Background(), "123") +``` + +After: + +```go +client.Board.GetAllSprints(context.Background(), "123", nil) +``` + +##### If you used `client.BoardService.GetAllSprintsWithOptions()`: + +Before: + +```go +client.Board.GetAllSprintsWithOptions(context.Background(), 123, &GetAllSprintsOptions{State: "active,future"}) +``` + +After: + +```go +client.Board.GetAllSprints(context.Background(), 123, &GetAllSprintsOptions{State: "active,future"}) +``` + ### Breaking changes * Jira On-Premise and Jira Cloud have now different clients, because the API differs @@ -173,6 +207,7 @@ client.Issue.Create(ctx, ...) * `client.NewRequestWithContext()` has been removed in favor of `client.NewRequest()`, which requires now a context as first argument * `client.NewMultiPartRequestWithContext()` has been removed in favor of `client.NewMultiPartRequest()`, which requires now a context as first argument * `context` is now a first class citizen in all API calls. Functions that had a suffix like `...WithContext` have been removed entirely. The API methods support the context now as first argument. +* `BoardService.GetAllSprints` has been removed and `BoardService.GetAllSprintsWithOptions` has been renamed to `BoardService.GetAllSprints` ### Features diff --git a/cloud/board.go b/cloud/board.go index 37b5167..992a378 100644 --- a/cloud/board.go +++ b/cloud/board.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net/http" - "strconv" "time" ) @@ -214,29 +213,11 @@ func (s *BoardService) DeleteBoard(ctx context.Context, boardID int) (*Board, *R return nil, resp, err } -// GetAllSprints will return all sprints from a board, for a given board Id. +// GetAllSprints returns all sprints from a board, for a given board ID. // This only includes sprints that the user has permission to view. // -// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint -func (s *BoardService) GetAllSprints(ctx context.Context, boardID string) ([]Sprint, *Response, error) { - id, err := strconv.Atoi(boardID) - if err != nil { - return nil, nil, err - } - - result, response, err := s.GetAllSprintsWithOptions(ctx, id, &GetAllSprintsOptions{}) - if err != nil { - return nil, nil, err - } - - return result.Values, response, nil -} - -// GetAllSprintsWithOptions will return sprints from a board, for a given board Id and filtering options -// This only includes sprints that the user has permission to view. -// -// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint -func (s *BoardService) GetAllSprintsWithOptions(ctx context.Context, boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { +// Jira API docs: https://developer.atlassian.com/cloud/jira/software/rest/api-group-board/#api-rest-agile-1-0-board-boardid-sprint-get +func (s *BoardService) GetAllSprints(ctx context.Context, boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/sprint", boardID) url, err := addOptions(apiEndpoint, options) if err != nil { diff --git a/cloud/board_test.go b/cloud/board_test.go index 2495dce..099c424 100644 --- a/cloud/board_test.go +++ b/cloud/board_test.go @@ -161,38 +161,6 @@ func TestBoardService_GetAllSprints(t *testing.T) { testAPIEndpoint := "/rest/agile/1.0/board/123/sprint" - raw, err := os.ReadFile("../testing/mock-data/sprints.json") - if err != nil { - t.Error(err.Error()) - } - - testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEndpoint) - fmt.Fprint(w, string(raw)) - }) - - sprints, _, err := testClient.Board.GetAllSprints(context.Background(), "123") - - if err != nil { - t.Errorf("Got error: %v", err) - } - - if sprints == nil { - t.Error("Expected sprint list. Got nil.") - } - - if len(sprints) != 4 { - t.Errorf("Expected 4 transitions. Got %d", len(sprints)) - } -} - -func TestBoardService_GetAllSprintsWithOptions(t *testing.T) { - setup() - defer teardown() - - testAPIEndpoint := "/rest/agile/1.0/board/123/sprint" - raw, err := os.ReadFile("../testing/mock-data/sprints_filtered.json") if err != nil { t.Error(err.Error()) @@ -204,7 +172,7 @@ func TestBoardService_GetAllSprintsWithOptions(t *testing.T) { fmt.Fprint(w, string(raw)) }) - sprints, _, err := testClient.Board.GetAllSprintsWithOptions(context.Background(), 123, &GetAllSprintsOptions{State: "active,future"}) + sprints, _, err := testClient.Board.GetAllSprints(context.Background(), 123, &GetAllSprintsOptions{State: "active,future"}) if err != nil { t.Errorf("Got error: %v", err) } diff --git a/onpremise/board.go b/onpremise/board.go index 1404520..8b92be7 100644 --- a/onpremise/board.go +++ b/onpremise/board.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net/http" - "strconv" "time" ) @@ -214,29 +213,11 @@ func (s *BoardService) DeleteBoard(ctx context.Context, boardID int) (*Board, *R return nil, resp, err } -// GetAllSprints will return all sprints from a board, for a given board Id. +// GetAllSprints returns all sprints from a board, for a given board ID. // This only includes sprints that the user has permission to view. // -// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint -func (s *BoardService) GetAllSprints(ctx context.Context, boardID string) ([]Sprint, *Response, error) { - id, err := strconv.Atoi(boardID) - if err != nil { - return nil, nil, err - } - - result, response, err := s.GetAllSprintsWithOptions(ctx, id, &GetAllSprintsOptions{}) - if err != nil { - return nil, nil, err - } - - return result.Values, response, nil -} - -// GetAllSprintsWithOptions will return sprints from a board, for a given board Id and filtering options -// This only includes sprints that the user has permission to view. -// -// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint -func (s *BoardService) GetAllSprintsWithOptions(ctx context.Context, boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { +// Jira API docs: https://developer.atlassian.com/cloud/jira/software/rest/api-group-board/#api-rest-agile-1-0-board-boardid-sprint-get +func (s *BoardService) GetAllSprints(ctx context.Context, boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/sprint", boardID) url, err := addOptions(apiEndpoint, options) if err != nil { diff --git a/onpremise/board_test.go b/onpremise/board_test.go index 3ec06aa..894876b 100644 --- a/onpremise/board_test.go +++ b/onpremise/board_test.go @@ -161,38 +161,6 @@ func TestBoardService_GetAllSprints(t *testing.T) { testAPIEndpoint := "/rest/agile/1.0/board/123/sprint" - raw, err := os.ReadFile("../testing/mock-data/sprints.json") - if err != nil { - t.Error(err.Error()) - } - - testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEndpoint) - fmt.Fprint(w, string(raw)) - }) - - sprints, _, err := testClient.Board.GetAllSprints(context.Background(), "123") - - if err != nil { - t.Errorf("Got error: %v", err) - } - - if sprints == nil { - t.Error("Expected sprint list. Got nil.") - } - - if len(sprints) != 4 { - t.Errorf("Expected 4 transitions. Got %d", len(sprints)) - } -} - -func TestBoardService_GetAllSprintsWithOptions(t *testing.T) { - setup() - defer teardown() - - testAPIEndpoint := "/rest/agile/1.0/board/123/sprint" - raw, err := os.ReadFile("../testing/mock-data/sprints_filtered.json") if err != nil { t.Error(err.Error()) @@ -204,7 +172,7 @@ func TestBoardService_GetAllSprintsWithOptions(t *testing.T) { fmt.Fprint(w, string(raw)) }) - sprints, _, err := testClient.Board.GetAllSprintsWithOptions(context.Background(), 123, &GetAllSprintsOptions{State: "active,future"}) + sprints, _, err := testClient.Board.GetAllSprints(context.Background(), 123, &GetAllSprintsOptions{State: "active,future"}) if err != nil { t.Errorf("Got error: %v", err) } From 295e4c79b1c596414ead33d8bb1de931efab11c1 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Mon, 12 Sep 2022 20:10:36 +0200 Subject: [PATCH 053/189] GroupService: `GroupService.Get` removed, `GroupService.GetWithOptions` renamed Related #294 --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ cloud/group.go | 26 +++----------------------- cloud/group_test.go | 19 ++----------------- onpremise/group.go | 26 +++----------------------- onpremise/group_test.go | 19 ++----------------- 5 files changed, 45 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8f5c9a..130f384 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -200,6 +200,40 @@ After: client.Board.GetAllSprints(context.Background(), 123, &GetAllSprintsOptions{State: "active,future"}) ``` +#### `GroupService.Get` removed, `GroupService.GetWithOptions` renamed + + +The function `client.GroupService.Get()` has been removed. +The function `client.GroupService.GetWithOptions()` has been renamed to `client.GroupService.Get()`. + +##### If you used `client.GroupService.Get()`: + +Before: + +```go +client.Group.Get(context.Background(), "default") +``` + +After: + +```go +client.Group.Get(context.Background(), "default", nil) +``` + +##### If you used `client.GroupService.GetWithOptions()`: + +Before: + +```go +client.Group.GetWithOptions(context.Background(), "default", &GroupSearchOptions{StartAt: 0, MaxResults: 2}) +``` + +After: + +```go +client.Group.Get(context.Background(), "default", &GroupSearchOptions{StartAt: 0, MaxResults: 2}) +``` + ### Breaking changes * Jira On-Premise and Jira Cloud have now different clients, because the API differs @@ -208,6 +242,7 @@ client.Board.GetAllSprints(context.Background(), 123, &GetAllSprintsOptions{Stat * `client.NewMultiPartRequestWithContext()` has been removed in favor of `client.NewMultiPartRequest()`, which requires now a context as first argument * `context` is now a first class citizen in all API calls. Functions that had a suffix like `...WithContext` have been removed entirely. The API methods support the context now as first argument. * `BoardService.GetAllSprints` has been removed and `BoardService.GetAllSprintsWithOptions` has been renamed to `BoardService.GetAllSprints` +* `GroupService.Get` has been removed and `GroupService.GetWithOptions` has been renamed to `GroupService.Get` ### Features diff --git a/cloud/group.go b/cloud/group.go index 5a3da1c..6bd765f 100644 --- a/cloud/group.go +++ b/cloud/group.go @@ -58,39 +58,19 @@ type GroupSearchOptions struct { IncludeInactiveUsers bool } -// Get returns a paginated list of users who are members of the specified group and its subgroups. +// Get returns a paginated list of members of the specified group and its subgroups. // Users in the page are ordered by user names. // User of this resource is required to have sysadmin or admin permissions. // // Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup // // WARNING: This API only returns the first page of group members -func (s *GroupService) Get(ctx context.Context, name string) ([]GroupMember, *Response, error) { - apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) - req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) - if err != nil { - return nil, nil, err - } - - group := new(groupMembersResult) - resp, err := s.client.Do(req, group) - if err != nil { - return nil, resp, err - } - - return group.Members, resp, nil -} - -// GetWithOptions returns a paginated list of members of the specified group and its subgroups. -// Users in the page are ordered by user names. -// User of this resource is required to have sysadmin or admin permissions. -// -// Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup -func (s *GroupService) GetWithOptions(ctx context.Context, name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { +func (s *GroupService) Get(ctx context.Context, name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { var apiEndpoint string if options == nil { apiEndpoint = fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) } else { + // TODO use addOptions apiEndpoint = fmt.Sprintf( "/rest/api/2/group/member?groupname=%s&startAt=%d&maxResults=%d&includeInactiveUsers=%t", url.QueryEscape(name), diff --git a/cloud/group_test.go b/cloud/group_test.go index 43fff23..596ac61 100644 --- a/cloud/group_test.go +++ b/cloud/group_test.go @@ -7,21 +7,6 @@ import ( "testing" ) -func TestGroupService_Get(t *testing.T) { - setup() - defer teardown() - testMux.HandleFunc("/rest/api/2/group/member", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, http.MethodGet) - testRequestURL(t, r, "/rest/api/2/group/member?groupname=default") - fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/group/member?includeInactiveUsers=false&maxResults=50&groupname=default&startAt=0","maxResults":50,"startAt":0,"total":2,"isLast":true,"values":[{"self":"http://www.example.com/jira/rest/api/2/user?username=michael","name":"michael","key":"michael","emailAddress":"michael@example.com","displayName":"MichaelScofield","active":true,"timeZone":"Australia/Sydney"},{"self":"http://www.example.com/jira/rest/api/2/user?username=alex","name":"alex","key":"alex","emailAddress":"alex@example.com","displayName":"AlexanderMahone","active":true,"timeZone":"Australia/Sydney"}]}`) - }) - if members, _, err := testClient.Group.Get(context.Background(), "default"); err != nil { - t.Errorf("Error given: %s", err) - } else if members == nil { - t.Error("Expected members. Group.Members is nil") - } -} - func TestGroupService_GetPage(t *testing.T) { setup() defer teardown() @@ -37,7 +22,7 @@ func TestGroupService_GetPage(t *testing.T) { t.Errorf("startAt %s", startAt) } }) - if page, resp, err := testClient.Group.GetWithOptions(context.Background(), "default", &GroupSearchOptions{ + if page, resp, err := testClient.Group.Get(context.Background(), "default", &GroupSearchOptions{ StartAt: 0, MaxResults: 2, IncludeInactiveUsers: false, @@ -55,7 +40,7 @@ func TestGroupService_GetPage(t *testing.T) { if resp.Total != 4 { t.Errorf("Expect Result Total to be 4, but is %d", resp.Total) } - if page, resp, err := testClient.Group.GetWithOptions(context.Background(), "default", &GroupSearchOptions{ + if page, resp, err := testClient.Group.Get(context.Background(), "default", &GroupSearchOptions{ StartAt: 2, MaxResults: 2, IncludeInactiveUsers: false, diff --git a/onpremise/group.go b/onpremise/group.go index 44074ee..07a3694 100644 --- a/onpremise/group.go +++ b/onpremise/group.go @@ -58,39 +58,19 @@ type GroupSearchOptions struct { IncludeInactiveUsers bool } -// Get returns a paginated list of users who are members of the specified group and its subgroups. +// Get returns a paginated list of members of the specified group and its subgroups. // Users in the page are ordered by user names. // User of this resource is required to have sysadmin or admin permissions. // // Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup // // WARNING: This API only returns the first page of group members -func (s *GroupService) Get(ctx context.Context, name string) ([]GroupMember, *Response, error) { - apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) - req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) - if err != nil { - return nil, nil, err - } - - group := new(groupMembersResult) - resp, err := s.client.Do(req, group) - if err != nil { - return nil, resp, err - } - - return group.Members, resp, nil -} - -// GetWithOptions returns a paginated list of members of the specified group and its subgroups. -// Users in the page are ordered by user names. -// User of this resource is required to have sysadmin or admin permissions. -// -// Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup -func (s *GroupService) GetWithOptions(ctx context.Context, name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { +func (s *GroupService) Get(ctx context.Context, name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { var apiEndpoint string if options == nil { apiEndpoint = fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) } else { + // TODO use addOptions apiEndpoint = fmt.Sprintf( "/rest/api/2/group/member?groupname=%s&startAt=%d&maxResults=%d&includeInactiveUsers=%t", url.QueryEscape(name), diff --git a/onpremise/group_test.go b/onpremise/group_test.go index 125db70..9ffd6ba 100644 --- a/onpremise/group_test.go +++ b/onpremise/group_test.go @@ -7,21 +7,6 @@ import ( "testing" ) -func TestGroupService_Get(t *testing.T) { - setup() - defer teardown() - testMux.HandleFunc("/rest/api/2/group/member", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, http.MethodGet) - testRequestURL(t, r, "/rest/api/2/group/member?groupname=default") - fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/group/member?includeInactiveUsers=false&maxResults=50&groupname=default&startAt=0","maxResults":50,"startAt":0,"total":2,"isLast":true,"values":[{"self":"http://www.example.com/jira/rest/api/2/user?username=michael","name":"michael","key":"michael","emailAddress":"michael@example.com","displayName":"MichaelScofield","active":true,"timeZone":"Australia/Sydney"},{"self":"http://www.example.com/jira/rest/api/2/user?username=alex","name":"alex","key":"alex","emailAddress":"alex@example.com","displayName":"AlexanderMahone","active":true,"timeZone":"Australia/Sydney"}]}`) - }) - if members, _, err := testClient.Group.Get(context.Background(), "default"); err != nil { - t.Errorf("Error given: %s", err) - } else if members == nil { - t.Error("Expected members. Group.Members is nil") - } -} - func TestGroupService_GetPage(t *testing.T) { setup() defer teardown() @@ -37,7 +22,7 @@ func TestGroupService_GetPage(t *testing.T) { t.Errorf("startAt %s", startAt) } }) - if page, resp, err := testClient.Group.GetWithOptions(context.Background(), "default", &GroupSearchOptions{ + if page, resp, err := testClient.Group.Get(context.Background(), "default", &GroupSearchOptions{ StartAt: 0, MaxResults: 2, IncludeInactiveUsers: false, @@ -55,7 +40,7 @@ func TestGroupService_GetPage(t *testing.T) { if resp.Total != 4 { t.Errorf("Expect Result Total to be 4, but is %d", resp.Total) } - if page, resp, err := testClient.Group.GetWithOptions(context.Background(), "default", &GroupSearchOptions{ + if page, resp, err := testClient.Group.Get(context.Background(), "default", &GroupSearchOptions{ StartAt: 2, MaxResults: 2, IncludeInactiveUsers: false, From e22ee9a510ec1a84eca3004d59a57bb11b75b971 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Mon, 12 Sep 2022 20:17:34 +0200 Subject: [PATCH 054/189] IssueService: `Issue.Update` removed, `Issue.UpdateWithOptions` renamed Related #294 --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++---- cloud/issue.go | 13 +++---------- cloud/issue_test.go | 2 +- onpremise/issue.go | 13 +++---------- onpremise/issue_test.go | 2 +- 5 files changed, 41 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 130f384..0689b12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -168,8 +168,6 @@ client.Issue.Create(ctx, ...) #### `BoardService.GetAllSprints` removed, `BoardService.GetAllSprintsWithOptions` renamed - -The function `client.BoardService.GetAllSprints()` has been removed. The function `client.BoardService.GetAllSprintsWithOptions()` has been renamed to `client.BoardService.GetAllSprints()`. ##### If you used `client.BoardService.GetAllSprints()`: @@ -202,8 +200,6 @@ client.Board.GetAllSprints(context.Background(), 123, &GetAllSprintsOptions{Stat #### `GroupService.Get` removed, `GroupService.GetWithOptions` renamed - -The function `client.GroupService.Get()` has been removed. The function `client.GroupService.GetWithOptions()` has been renamed to `client.GroupService.Get()`. ##### If you used `client.GroupService.Get()`: @@ -234,6 +230,38 @@ After: client.Group.Get(context.Background(), "default", &GroupSearchOptions{StartAt: 0, MaxResults: 2}) ``` +#### `Issue.Update` removed, `Issue.UpdateWithOptions` renamed + +The function `client.Issue.UpdateWithOptions()` has been renamed to `client.Issue.Update()`. + +##### If you used `client.Issue.Update()`: + +Before: + +```go +client.Issue.Update(context.Background(), issue) +``` + +After: + +```go +client.Issue.Update(context.Background(), issue, nil) +``` + +##### If you used `client.Issue.UpdateWithOptions()`: + +Before: + +```go +client.Issue.UpdateWithOptions(context.Background(), issue, nil) +``` + +After: + +```go +client.Issue.Update(context.Background(), issue, nil) +``` + ### Breaking changes * Jira On-Premise and Jira Cloud have now different clients, because the API differs @@ -243,6 +271,7 @@ client.Group.Get(context.Background(), "default", &GroupSearchOptions{StartAt: 0 * `context` is now a first class citizen in all API calls. Functions that had a suffix like `...WithContext` have been removed entirely. The API methods support the context now as first argument. * `BoardService.GetAllSprints` has been removed and `BoardService.GetAllSprintsWithOptions` has been renamed to `BoardService.GetAllSprints` * `GroupService.Get` has been removed and `GroupService.GetWithOptions` has been renamed to `GroupService.Get` +* `Issue.Update` has been removed and `Issue.UpdateWithOptions` has been renamed to `Issue.Update` ### Features diff --git a/cloud/issue.go b/cloud/issue.go index b459cd0..99dc8f0 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -804,12 +804,12 @@ func (s *IssueService) Create(ctx context.Context, issue *Issue) (*Issue, *Respo return responseIssue, resp, nil } -// UpdateWithOptions updates an issue from a JSON representation, +// Update updates an issue from a JSON representation, // while also specifying query params. The issue is found by key. // -// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-editIssue +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issues/#api-rest-api-2-issue-issueidorkey-put // Caller must close resp.Body -func (s *IssueService) UpdateWithOptions(ctx context.Context, issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) { +func (s *IssueService) Update(ctx context.Context, issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", issue.Key) url, err := addOptions(apiEndpoint, opts) if err != nil { @@ -831,13 +831,6 @@ func (s *IssueService) UpdateWithOptions(ctx context.Context, issue *Issue, opts return &ret, resp, nil } -// Update updates an issue from a JSON representation. The issue is found by key. -// -// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-editIssue -func (s *IssueService) Update(ctx context.Context, issue *Issue) (*Issue, *Response, error) { - return s.UpdateWithOptions(ctx, issue, nil) -} - // UpdateIssue updates an issue from a JSON representation. The issue is found by key. // // https://docs.atlassian.com/jira/REST/7.4.0/#api/2/issue-editIssue diff --git a/cloud/issue_test.go b/cloud/issue_test.go index 3618b4c..bae570e 100644 --- a/cloud/issue_test.go +++ b/cloud/issue_test.go @@ -146,7 +146,7 @@ func TestIssueService_Update(t *testing.T) { Description: "example bug report", }, } - issue, _, err := testClient.Issue.Update(context.Background(), i) + issue, _, err := testClient.Issue.Update(context.Background(), i, nil) if issue == nil { t.Error("Expected issue. Issue is nil") } diff --git a/onpremise/issue.go b/onpremise/issue.go index 45aade5..b549afe 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -804,12 +804,12 @@ func (s *IssueService) Create(ctx context.Context, issue *Issue) (*Issue, *Respo return responseIssue, resp, nil } -// UpdateWithOptions updates an issue from a JSON representation, +// Update updates an issue from a JSON representation, // while also specifying query params. The issue is found by key. // -// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-editIssue +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issues/#api-rest-api-2-issue-issueidorkey-put // Caller must close resp.Body -func (s *IssueService) UpdateWithOptions(ctx context.Context, issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) { +func (s *IssueService) Update(ctx context.Context, issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", issue.Key) url, err := addOptions(apiEndpoint, opts) if err != nil { @@ -831,13 +831,6 @@ func (s *IssueService) UpdateWithOptions(ctx context.Context, issue *Issue, opts return &ret, resp, nil } -// Update updates an issue from a JSON representation. The issue is found by key. -// -// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-editIssue -func (s *IssueService) Update(ctx context.Context, issue *Issue) (*Issue, *Response, error) { - return s.UpdateWithOptions(ctx, issue, nil) -} - // UpdateIssue updates an issue from a JSON representation. The issue is found by key. // // https://docs.atlassian.com/jira/REST/7.4.0/#api/2/issue-editIssue diff --git a/onpremise/issue_test.go b/onpremise/issue_test.go index 36186f7..88bdc20 100644 --- a/onpremise/issue_test.go +++ b/onpremise/issue_test.go @@ -146,7 +146,7 @@ func TestIssueService_Update(t *testing.T) { Description: "example bug report", }, } - issue, _, err := testClient.Issue.Update(context.Background(), i) + issue, _, err := testClient.Issue.Update(context.Background(), i, nil) if issue == nil { t.Error("Expected issue. Issue is nil") } From 5a66ee4ef519aae172e0884331e2126a5b84b345 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Mon, 12 Sep 2022 20:41:37 +0200 Subject: [PATCH 055/189] IssueService: `Issue.GetCreateMeta` removed, `Issue.GetCreateMetaWithOptions` renamed Related #294 --- CHANGELOG.md | 32 +++ cloud/metaissue.go | 9 +- cloud/metaissue_test.go | 376 +----------------------------------- onpremise/metaissue.go | 9 +- onpremise/metaissue_test.go | 376 +----------------------------------- 5 files changed, 40 insertions(+), 762 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0689b12..7c5051d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -262,6 +262,38 @@ After: client.Issue.Update(context.Background(), issue, nil) ``` +#### `Issue.GetCreateMeta` removed, `Issue.GetCreateMetaWithOptions` renamed + +The function `client.Issue.GetCreateMetaWithOptions()` has been renamed to `client.Issue.GetCreateMeta()`. + +##### If you used `client.Issue.GetCreateMeta()`: + +Before: + +```go +client.Issue.GetCreateMeta(context.Background(), "SPN") +``` + +After: + +```go +client.Issue.GetCreateMetaWithOptions(ctx, &GetQueryOptions{ProjectKeys: "SPN", Expand: "projects.issuetypes.fields"}) +``` + +##### If you used `client.Issue.GetCreateMetaWithOptions()`: + +Before: + +```go +client.Issue.GetCreateMetaWithOptions(ctx, &GetQueryOptions{ProjectKeys: "SPN", Expand: "projects.issuetypes.fields"}) +``` + +After: + +```go +client.Issue.GetCreateMeta(ctx, &GetQueryOptions{ProjectKeys: "SPN", Expand: "projects.issuetypes.fields"}) +``` + ### Breaking changes * Jira On-Premise and Jira Cloud have now different clients, because the API differs diff --git a/cloud/metaissue.go b/cloud/metaissue.go index 1ae9bc9..3e6af1e 100644 --- a/cloud/metaissue.go +++ b/cloud/metaissue.go @@ -49,13 +49,8 @@ type MetaIssueType struct { Fields tcontainer.MarshalMap `json:"fields,omitempty"` } -// GetCreateMeta makes the api call to get the meta information required to create a ticket -func (s *IssueService) GetCreateMeta(ctx context.Context, projectkeys string) (*CreateMetaInfo, *Response, error) { - return s.GetCreateMetaWithOptions(ctx, &GetQueryOptions{ProjectKeys: projectkeys, Expand: "projects.issuetypes.fields"}) -} - -// GetCreateMetaWithOptions makes the api call to get the meta information without requiring to have a projectKey -func (s *IssueService) GetCreateMetaWithOptions(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { +// GetCreateMeta makes the api call to get the meta information without requiring to have a projectKey +func (s *IssueService) GetCreateMeta(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { apiEndpoint := "rest/api/2/issue/createmeta" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/cloud/metaissue_test.go b/cloud/metaissue_test.go index 7292c67..2182483 100644 --- a/cloud/metaissue_test.go +++ b/cloud/metaissue_test.go @@ -8,378 +8,6 @@ import ( "testing" ) -func TestIssueService_GetCreateMeta_Success(t *testing.T) { - setup() - defer teardown() - - testAPIEndpoint := "/rest/api/2/issue/createmeta" - - testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEndpoint) - - fmt.Fprint(w, `{ - "expand": "projects", - "projects": [{ - "expand": "issuetypes", - "self": "https://my.jira.com/rest/api/2/project/11300", - "id": "11300", - "key": "SPN", - "name": "Super Project Name", - "avatarUrls": { - "48x48": "https://my.jira.com/secure/projectavatar?pid=11300&avatarId=14405", - "24x24": "https://my.jira.com/secure/projectavatar?size=small&pid=11300&avatarId=14405", - "16x16": "https://my.jira.com/secure/projectavatar?size=xsmall&pid=11300&avatarId=14405", - "32x32": "https://my.jira.com/secure/projectavatar?size=medium&pid=11300&avatarId=14405" - }, - "issuetypes": [{ - "self": "https://my.jira.com/rest/api/2/issuetype/6", - "id": "6", - "description": "An issue which ideally should be able to be completed in one step", - "iconUrl": "https://my.jira.com/secure/viewavatar?size=xsmall&avatarId=14006&avatarType=issuetype", - "name": "Request", - "subtask": false, - "expand": "fields", - "fields": { - "summary": { - "required": true, - "schema": { - "type": "string", - "system": "summary" - }, - "name": "Summary", - "hasDefaultValue": false, - "operations": [ - "set" - ] - }, - "issuetype": { - "required": true, - "schema": { - "type": "issuetype", - "system": "issuetype" - }, - "name": "Issue Type", - "hasDefaultValue": false, - "operations": [ - - ], - "allowedValues": [{ - "self": "https://my.jira.com/rest/api/2/issuetype/6", - "id": "6", - "description": "An issue which ideally should be able to be completed in one step", - "iconUrl": "https://my.jira.com/secure/viewavatar?size=xsmall&avatarId=14006&avatarType=issuetype", - "name": "Request", - "subtask": false, - "avatarId": 14006 - }] - }, - "components": { - "required": true, - "schema": { - "type": "array", - "items": "component", - "system": "components" - }, - "name": "Component/s", - "hasDefaultValue": false, - "operations": [ - "add", - "set", - "remove" - ], - "allowedValues": [{ - "self": "https://my.jira.com/rest/api/2/component/14144", - "id": "14144", - "name": "Build automation", - "description": "Jenkins, webhooks, etc." - }, { - "self": "https://my.jira.com/rest/api/2/component/14149", - "id": "14149", - "name": "Caches and noSQL", - "description": "Cassandra, Memcached, Redis, Twemproxy, Xcache" - }, { - "self": "https://my.jira.com/rest/api/2/component/14152", - "id": "14152", - "name": "Cloud services", - "description": "AWS and similar services" - }, { - "self": "https://my.jira.com/rest/api/2/component/14147", - "id": "14147", - "name": "Code quality tools", - "description": "Code sniffer, Sonar" - }, { - "self": "https://my.jira.com/rest/api/2/component/14156", - "id": "14156", - "name": "Configuration management and provisioning", - "description": "Apache/PHP modules, Consul, Salt" - }, { - "self": "https://my.jira.com/rest/api/2/component/13606", - "id": "13606", - "name": "Cronjobs", - "description": "Cronjobs in general" - }, { - "self": "https://my.jira.com/rest/api/2/component/14150", - "id": "14150", - "name": "Data pipelines and queues", - "description": "Kafka, RabbitMq" - }, { - "self": "https://my.jira.com/rest/api/2/component/14159", - "id": "14159", - "name": "Database", - "description": "MySQL related problems" - }, { - "self": "https://my.jira.com/rest/api/2/component/14314", - "id": "14314", - "name": "Documentation" - }, { - "self": "https://my.jira.com/rest/api/2/component/14151", - "id": "14151", - "name": "Git", - "description": "Bitbucket, GitHub, GitLab, Git in general" - }, { - "self": "https://my.jira.com/rest/api/2/component/14155", - "id": "14155", - "name": "HTTP services", - "description": "CDN, HaProxy, HTTP, Varnish" - }, { - "self": "https://my.jira.com/rest/api/2/component/14154", - "id": "14154", - "name": "Job and service scheduling", - "description": "Chronos, Docker, Marathon, Mesos" - }, { - "self": "https://my.jira.com/rest/api/2/component/14158", - "id": "14158", - "name": "Legacy", - "description": "Everything related to legacy" - }, { - "self": "https://my.jira.com/rest/api/2/component/14157", - "id": "14157", - "name": "Monitoring", - "description": "Collectd, Nagios, Monitoring in general" - }, { - "self": "https://my.jira.com/rest/api/2/component/14148", - "id": "14148", - "name": "Other services" - }, { - "self": "https://my.jira.com/rest/api/2/component/13602", - "id": "13602", - "name": "Package management", - "description": "Composer, Medusa, Satis" - }, { - "self": "https://my.jira.com/rest/api/2/component/14145", - "id": "14145", - "name": "Release", - "description": "Directory config, release queries, rewrite rules" - }, { - "self": "https://my.jira.com/rest/api/2/component/14146", - "id": "14146", - "name": "Staging systems and VMs", - "description": "Stage, QA machines, KVMs,Vagrant" - }, { - "self": "https://my.jira.com/rest/api/2/component/14153", - "id": "14153", - "name": "Blog" - }, { - "self": "https://my.jira.com/rest/api/2/component/14143", - "id": "14143", - "name": "Test automation", - "description": "Testing infrastructure in general" - }, { - "self": "https://my.jira.com/rest/api/2/component/14221", - "id": "14221", - "name": "Internal Infrastructure" - }] - }, - "attachment": { - "required": false, - "schema": { - "type": "array", - "items": "attachment", - "system": "attachment" - }, - "name": "Attachment", - "hasDefaultValue": false, - "operations": [ - - ] - }, - "duedate": { - "required": false, - "schema": { - "type": "date", - "system": "duedate" - }, - "name": "Due Date", - "hasDefaultValue": false, - "operations": [ - "set" - ] - }, - "description": { - "required": false, - "schema": { - "type": "string", - "system": "description" - }, - "name": "Description", - "hasDefaultValue": false, - "operations": [ - "set" - ] - }, - "customfield_10806": { - "required": false, - "schema": { - "type": "any", - "custom": "com.pyxis.greenhopper.jira:gh-epic-link", - "customId": 10806 - }, - "name": "Epic Link", - "hasDefaultValue": false, - "operations": [ - "set" - ] - }, - "project": { - "required": true, - "schema": { - "type": "project", - "system": "project" - }, - "name": "Project", - "hasDefaultValue": false, - "operations": [ - "set" - ], - "allowedValues": [{ - "self": "https://my.jira.com/rest/api/2/project/11300", - "id": "11300", - "key": "SPN", - "name": "Super Project Name", - "avatarUrls": { - "48x48": "https://my.jira.com/secure/projectavatar?pid=11300&avatarId=14405", - "24x24": "https://my.jira.com/secure/projectavatar?size=small&pid=11300&avatarId=14405", - "16x16": "https://my.jira.com/secure/projectavatar?size=xsmall&pid=11300&avatarId=14405", - "32x32": "https://my.jira.com/secure/projectavatar?size=medium&pid=11300&avatarId=14405" - }, - "projectCategory": { - "self": "https://my.jira.com/rest/api/2/projectCategory/10100", - "id": "10100", - "description": "", - "name": "Product & Development" - } - }] - }, - "assignee": { - "required": true, - "schema": { - "type": "user", - "system": "assignee" - }, - "name": "Assignee", - "autoCompleteUrl": "https://my.jira.com/rest/api/latest/user/assignable/search?issueKey=null&username=", - "hasDefaultValue": true, - "operations": [ - "set" - ] - }, - "priority": { - "required": false, - "schema": { - "type": "priority", - "system": "priority" - }, - "name": "Priority", - "hasDefaultValue": true, - "operations": [ - "set" - ], - "allowedValues": [{ - "self": "https://my.jira.com/rest/api/2/priority/1", - "iconUrl": "https://my.jira.com/images/icons/priorities/blocker.svg", - "name": "Immediate", - "id": "1" - }, { - "self": "https://my.jira.com/rest/api/2/priority/2", - "iconUrl": "https://my.jira.com/images/icons/priorities/critical.svg", - "name": "Urgent", - "id": "2" - }, { - "self": "https://my.jira.com/rest/api/2/priority/3", - "iconUrl": "https://my.jira.com/images/icons/priorities/major.svg", - "name": "High", - "id": "3" - }, { - "self": "https://my.jira.com/rest/api/2/priority/6", - "iconUrl": "https://my.jira.com/images/icons/priorities/moderate.svg", - "name": "Moderate", - "id": "6" - }, { - "self": "https://my.jira.com/rest/api/2/priority/4", - "iconUrl": "https://my.jira.com/images/icons/priorities/minor.svg", - "name": "Normal", - "id": "4" - }, { - "self": "https://my.jira.com/rest/api/2/priority/5", - "iconUrl": "https://my.jira.com/images/icons/priorities/trivial.svg", - "name": "Low", - "id": "5" - }] - }, - "labels": { - "required": false, - "schema": { - "type": "array", - "items": "string", - "system": "labels" - }, - "name": "Labels", - "autoCompleteUrl": "https://my.jira.com/rest/api/1.0/labels/suggest?query=", - "hasDefaultValue": false, - "operations": [ - "add", - "set", - "remove" - ] - } - } - }] - }] - }`) - }) - - issue, _, err := testClient.Issue.GetCreateMeta(context.Background(), "SPN") - if err != nil { - t.Errorf("Expected nil error but got %s", err) - } - - if len(issue.Projects) != 1 { - t.Errorf("Expected 1 project, got %d", len(issue.Projects)) - } - for _, project := range issue.Projects { - if len(project.IssueTypes) != 1 { - t.Errorf("Expected 1 issueTypes, got %d", len(project.IssueTypes)) - } - for _, issueTypes := range project.IssueTypes { - requiredFields := 0 - fields := issueTypes.Fields - for _, value := range fields { - for key, value := range value.(map[string]interface{}) { - if key == "required" && value == true { - requiredFields = requiredFields + 1 - } - } - - } - if requiredFields != 5 { - t.Errorf("Expected 5 required fields from Create Meta information, got %d", requiredFields) - } - } - } - -} - func TestIssueService_GetEditMeta_Success(t *testing.T) { setup() defer teardown() @@ -457,7 +85,7 @@ func TestIssueService_GetEditMeta_Fail(t *testing.T) { } } -func TestMetaIssueType_GetCreateMetaWithOptions(t *testing.T) { +func TestMetaIssueType_GetCreateMeta(t *testing.T) { setup() defer teardown() @@ -798,7 +426,7 @@ func TestMetaIssueType_GetCreateMetaWithOptions(t *testing.T) { }`) }) - issue, _, err := testClient.Issue.GetCreateMetaWithOptions(context.Background(), &GetQueryOptions{Expand: "projects.issuetypes.fields"}) + issue, _, err := testClient.Issue.GetCreateMeta(context.Background(), &GetQueryOptions{Expand: "projects.issuetypes.fields"}) if err != nil { t.Errorf("Expected nil error but got %s", err) } diff --git a/onpremise/metaissue.go b/onpremise/metaissue.go index 5a5e970..a1b104c 100644 --- a/onpremise/metaissue.go +++ b/onpremise/metaissue.go @@ -49,13 +49,8 @@ type MetaIssueType struct { Fields tcontainer.MarshalMap `json:"fields,omitempty"` } -// GetCreateMeta makes the api call to get the meta information required to create a ticket -func (s *IssueService) GetCreateMeta(ctx context.Context, projectkeys string) (*CreateMetaInfo, *Response, error) { - return s.GetCreateMetaWithOptions(ctx, &GetQueryOptions{ProjectKeys: projectkeys, Expand: "projects.issuetypes.fields"}) -} - -// GetCreateMetaWithOptions makes the api call to get the meta information without requiring to have a projectKey -func (s *IssueService) GetCreateMetaWithOptions(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { +// GetCreateMeta makes the api call to get the meta information without requiring to have a projectKey +func (s *IssueService) GetCreateMeta(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { apiEndpoint := "rest/api/2/issue/createmeta" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/onpremise/metaissue_test.go b/onpremise/metaissue_test.go index 9a5c22d..a138076 100644 --- a/onpremise/metaissue_test.go +++ b/onpremise/metaissue_test.go @@ -8,378 +8,6 @@ import ( "testing" ) -func TestIssueService_GetCreateMeta_Success(t *testing.T) { - setup() - defer teardown() - - testAPIEndpoint := "/rest/api/2/issue/createmeta" - - testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEndpoint) - - fmt.Fprint(w, `{ - "expand": "projects", - "projects": [{ - "expand": "issuetypes", - "self": "https://my.jira.com/rest/api/2/project/11300", - "id": "11300", - "key": "SPN", - "name": "Super Project Name", - "avatarUrls": { - "48x48": "https://my.jira.com/secure/projectavatar?pid=11300&avatarId=14405", - "24x24": "https://my.jira.com/secure/projectavatar?size=small&pid=11300&avatarId=14405", - "16x16": "https://my.jira.com/secure/projectavatar?size=xsmall&pid=11300&avatarId=14405", - "32x32": "https://my.jira.com/secure/projectavatar?size=medium&pid=11300&avatarId=14405" - }, - "issuetypes": [{ - "self": "https://my.jira.com/rest/api/2/issuetype/6", - "id": "6", - "description": "An issue which ideally should be able to be completed in one step", - "iconUrl": "https://my.jira.com/secure/viewavatar?size=xsmall&avatarId=14006&avatarType=issuetype", - "name": "Request", - "subtask": false, - "expand": "fields", - "fields": { - "summary": { - "required": true, - "schema": { - "type": "string", - "system": "summary" - }, - "name": "Summary", - "hasDefaultValue": false, - "operations": [ - "set" - ] - }, - "issuetype": { - "required": true, - "schema": { - "type": "issuetype", - "system": "issuetype" - }, - "name": "Issue Type", - "hasDefaultValue": false, - "operations": [ - - ], - "allowedValues": [{ - "self": "https://my.jira.com/rest/api/2/issuetype/6", - "id": "6", - "description": "An issue which ideally should be able to be completed in one step", - "iconUrl": "https://my.jira.com/secure/viewavatar?size=xsmall&avatarId=14006&avatarType=issuetype", - "name": "Request", - "subtask": false, - "avatarId": 14006 - }] - }, - "components": { - "required": true, - "schema": { - "type": "array", - "items": "component", - "system": "components" - }, - "name": "Component/s", - "hasDefaultValue": false, - "operations": [ - "add", - "set", - "remove" - ], - "allowedValues": [{ - "self": "https://my.jira.com/rest/api/2/component/14144", - "id": "14144", - "name": "Build automation", - "description": "Jenkins, webhooks, etc." - }, { - "self": "https://my.jira.com/rest/api/2/component/14149", - "id": "14149", - "name": "Caches and noSQL", - "description": "Cassandra, Memcached, Redis, Twemproxy, Xcache" - }, { - "self": "https://my.jira.com/rest/api/2/component/14152", - "id": "14152", - "name": "Cloud services", - "description": "AWS and similar services" - }, { - "self": "https://my.jira.com/rest/api/2/component/14147", - "id": "14147", - "name": "Code quality tools", - "description": "Code sniffer, Sonar" - }, { - "self": "https://my.jira.com/rest/api/2/component/14156", - "id": "14156", - "name": "Configuration management and provisioning", - "description": "Apache/PHP modules, Consul, Salt" - }, { - "self": "https://my.jira.com/rest/api/2/component/13606", - "id": "13606", - "name": "Cronjobs", - "description": "Cronjobs in general" - }, { - "self": "https://my.jira.com/rest/api/2/component/14150", - "id": "14150", - "name": "Data pipelines and queues", - "description": "Kafka, RabbitMq" - }, { - "self": "https://my.jira.com/rest/api/2/component/14159", - "id": "14159", - "name": "Database", - "description": "MySQL related problems" - }, { - "self": "https://my.jira.com/rest/api/2/component/14314", - "id": "14314", - "name": "Documentation" - }, { - "self": "https://my.jira.com/rest/api/2/component/14151", - "id": "14151", - "name": "Git", - "description": "Bitbucket, GitHub, GitLab, Git in general" - }, { - "self": "https://my.jira.com/rest/api/2/component/14155", - "id": "14155", - "name": "HTTP services", - "description": "CDN, HaProxy, HTTP, Varnish" - }, { - "self": "https://my.jira.com/rest/api/2/component/14154", - "id": "14154", - "name": "Job and service scheduling", - "description": "Chronos, Docker, Marathon, Mesos" - }, { - "self": "https://my.jira.com/rest/api/2/component/14158", - "id": "14158", - "name": "Legacy", - "description": "Everything related to legacy" - }, { - "self": "https://my.jira.com/rest/api/2/component/14157", - "id": "14157", - "name": "Monitoring", - "description": "Collectd, Nagios, Monitoring in general" - }, { - "self": "https://my.jira.com/rest/api/2/component/14148", - "id": "14148", - "name": "Other services" - }, { - "self": "https://my.jira.com/rest/api/2/component/13602", - "id": "13602", - "name": "Package management", - "description": "Composer, Medusa, Satis" - }, { - "self": "https://my.jira.com/rest/api/2/component/14145", - "id": "14145", - "name": "Release", - "description": "Directory config, release queries, rewrite rules" - }, { - "self": "https://my.jira.com/rest/api/2/component/14146", - "id": "14146", - "name": "Staging systems and VMs", - "description": "Stage, QA machines, KVMs,Vagrant" - }, { - "self": "https://my.jira.com/rest/api/2/component/14153", - "id": "14153", - "name": "Blog" - }, { - "self": "https://my.jira.com/rest/api/2/component/14143", - "id": "14143", - "name": "Test automation", - "description": "Testing infrastructure in general" - }, { - "self": "https://my.jira.com/rest/api/2/component/14221", - "id": "14221", - "name": "Internal Infrastructure" - }] - }, - "attachment": { - "required": false, - "schema": { - "type": "array", - "items": "attachment", - "system": "attachment" - }, - "name": "Attachment", - "hasDefaultValue": false, - "operations": [ - - ] - }, - "duedate": { - "required": false, - "schema": { - "type": "date", - "system": "duedate" - }, - "name": "Due Date", - "hasDefaultValue": false, - "operations": [ - "set" - ] - }, - "description": { - "required": false, - "schema": { - "type": "string", - "system": "description" - }, - "name": "Description", - "hasDefaultValue": false, - "operations": [ - "set" - ] - }, - "customfield_10806": { - "required": false, - "schema": { - "type": "any", - "custom": "com.pyxis.greenhopper.jira:gh-epic-link", - "customId": 10806 - }, - "name": "Epic Link", - "hasDefaultValue": false, - "operations": [ - "set" - ] - }, - "project": { - "required": true, - "schema": { - "type": "project", - "system": "project" - }, - "name": "Project", - "hasDefaultValue": false, - "operations": [ - "set" - ], - "allowedValues": [{ - "self": "https://my.jira.com/rest/api/2/project/11300", - "id": "11300", - "key": "SPN", - "name": "Super Project Name", - "avatarUrls": { - "48x48": "https://my.jira.com/secure/projectavatar?pid=11300&avatarId=14405", - "24x24": "https://my.jira.com/secure/projectavatar?size=small&pid=11300&avatarId=14405", - "16x16": "https://my.jira.com/secure/projectavatar?size=xsmall&pid=11300&avatarId=14405", - "32x32": "https://my.jira.com/secure/projectavatar?size=medium&pid=11300&avatarId=14405" - }, - "projectCategory": { - "self": "https://my.jira.com/rest/api/2/projectCategory/10100", - "id": "10100", - "description": "", - "name": "Product & Development" - } - }] - }, - "assignee": { - "required": true, - "schema": { - "type": "user", - "system": "assignee" - }, - "name": "Assignee", - "autoCompleteUrl": "https://my.jira.com/rest/api/latest/user/assignable/search?issueKey=null&username=", - "hasDefaultValue": true, - "operations": [ - "set" - ] - }, - "priority": { - "required": false, - "schema": { - "type": "priority", - "system": "priority" - }, - "name": "Priority", - "hasDefaultValue": true, - "operations": [ - "set" - ], - "allowedValues": [{ - "self": "https://my.jira.com/rest/api/2/priority/1", - "iconUrl": "https://my.jira.com/images/icons/priorities/blocker.svg", - "name": "Immediate", - "id": "1" - }, { - "self": "https://my.jira.com/rest/api/2/priority/2", - "iconUrl": "https://my.jira.com/images/icons/priorities/critical.svg", - "name": "Urgent", - "id": "2" - }, { - "self": "https://my.jira.com/rest/api/2/priority/3", - "iconUrl": "https://my.jira.com/images/icons/priorities/major.svg", - "name": "High", - "id": "3" - }, { - "self": "https://my.jira.com/rest/api/2/priority/6", - "iconUrl": "https://my.jira.com/images/icons/priorities/moderate.svg", - "name": "Moderate", - "id": "6" - }, { - "self": "https://my.jira.com/rest/api/2/priority/4", - "iconUrl": "https://my.jira.com/images/icons/priorities/minor.svg", - "name": "Normal", - "id": "4" - }, { - "self": "https://my.jira.com/rest/api/2/priority/5", - "iconUrl": "https://my.jira.com/images/icons/priorities/trivial.svg", - "name": "Low", - "id": "5" - }] - }, - "labels": { - "required": false, - "schema": { - "type": "array", - "items": "string", - "system": "labels" - }, - "name": "Labels", - "autoCompleteUrl": "https://my.jira.com/rest/api/1.0/labels/suggest?query=", - "hasDefaultValue": false, - "operations": [ - "add", - "set", - "remove" - ] - } - } - }] - }] - }`) - }) - - issue, _, err := testClient.Issue.GetCreateMeta(context.Background(), "SPN") - if err != nil { - t.Errorf("Expected nil error but got %s", err) - } - - if len(issue.Projects) != 1 { - t.Errorf("Expected 1 project, got %d", len(issue.Projects)) - } - for _, project := range issue.Projects { - if len(project.IssueTypes) != 1 { - t.Errorf("Expected 1 issueTypes, got %d", len(project.IssueTypes)) - } - for _, issueTypes := range project.IssueTypes { - requiredFields := 0 - fields := issueTypes.Fields - for _, value := range fields { - for key, value := range value.(map[string]interface{}) { - if key == "required" && value == true { - requiredFields = requiredFields + 1 - } - } - - } - if requiredFields != 5 { - t.Errorf("Expected 5 required fields from Create Meta information, got %d", requiredFields) - } - } - } - -} - func TestIssueService_GetEditMeta_Success(t *testing.T) { setup() defer teardown() @@ -457,7 +85,7 @@ func TestIssueService_GetEditMeta_Fail(t *testing.T) { } } -func TestMetaIssueType_GetCreateMetaWithOptions(t *testing.T) { +func TestMetaIssueType_GetCreateMeta(t *testing.T) { setup() defer teardown() @@ -798,7 +426,7 @@ func TestMetaIssueType_GetCreateMetaWithOptions(t *testing.T) { }`) }) - issue, _, err := testClient.Issue.GetCreateMetaWithOptions(context.Background(), &GetQueryOptions{Expand: "projects.issuetypes.fields"}) + issue, _, err := testClient.Issue.GetCreateMeta(context.Background(), &GetQueryOptions{Expand: "projects.issuetypes.fields"}) if err != nil { t.Errorf("Expected nil error but got %s", err) } From d2547bda5fe2e5a9115a6af13d6df97c47aa5686 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Mon, 12 Sep 2022 20:49:47 +0200 Subject: [PATCH 056/189] Project Service: `Project.GetList` removed, `Project.ListWithOptions` renamed to `Project.GetAll` Related #294 --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ cloud/project.go | 15 ++++----------- cloud/project_test.go | 28 ++-------------------------- onpremise/project.go | 15 ++++----------- onpremise/project_test.go | 28 ++-------------------------- 5 files changed, 46 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c5051d..21d935f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -294,6 +294,38 @@ After: client.Issue.GetCreateMeta(ctx, &GetQueryOptions{ProjectKeys: "SPN", Expand: "projects.issuetypes.fields"}) ``` +#### `Project.GetList` removed, `Project.ListWithOptions` renamed + +The function `client.Project.ListWithOptions()` has been renamed to `client.Project.GetAll()`. + +##### If you used `client.Project.GetList()`: + +Before: + +```go +client.Project.GetList(context.Background()) +``` + +After: + +```go +client.Project.GetAll(context.Background(), nil) +``` + +##### If you used `client.Project.ListWithOptions()`: + +Before: + +```go +client.Project.ListWithOptions(ctx, &GetQueryOptions{}) +``` + +After: + +```go +client.Project.GetAll(ctx, &GetQueryOptions{}) +``` + ### Breaking changes * Jira On-Premise and Jira Cloud have now different clients, because the API differs @@ -304,6 +336,8 @@ client.Issue.GetCreateMeta(ctx, &GetQueryOptions{ProjectKeys: "SPN", Expand: "pr * `BoardService.GetAllSprints` has been removed and `BoardService.GetAllSprintsWithOptions` has been renamed to `BoardService.GetAllSprints` * `GroupService.Get` has been removed and `GroupService.GetWithOptions` has been renamed to `GroupService.Get` * `Issue.Update` has been removed and `Issue.UpdateWithOptions` has been renamed to `Issue.Update` +* `Issue.GetCreateMeta` has been removed and `Issue.GetCreateMetaWithOptions` has been renamed to `Issue.GetCreateMeta` +* `Project.GetList` has been removed and `Project.ListWithOptions` has been renamed to `Project.GetAll` ### Features diff --git a/cloud/project.go b/cloud/project.go index 2480ece..3fa6d49 100644 --- a/cloud/project.go +++ b/cloud/project.go @@ -80,18 +80,11 @@ type PermissionScheme struct { Permissions []Permission `json:"permissions" structs:"permissions,omitempty"` } -// GetList gets all projects form Jira +// GetAll returns all projects form Jira with optional query params, like &GetQueryOptions{Expand: "issueTypes"} to get +// a list of all projects and their supported issuetypes. // -// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects -func (s *ProjectService) GetList(ctx context.Context) (*ProjectList, *Response, error) { - return s.ListWithOptions(ctx, &GetQueryOptions{}) -} - -// ListWithOptions gets all projects form Jira with optional query params, like &GetQueryOptions{Expand: "issueTypes"} to get -// a list of all projects and their supported issuetypes -// -// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects -func (s *ProjectService) ListWithOptions(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-projects/#api-rest-api-2-project-get +func (s *ProjectService) GetAll(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { apiEndpoint := "rest/api/2/project" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { diff --git a/cloud/project_test.go b/cloud/project_test.go index f21cceb..046df3d 100644 --- a/cloud/project_test.go +++ b/cloud/project_test.go @@ -8,31 +8,7 @@ import ( "testing" ) -func TestProjectService_GetList(t *testing.T) { - setup() - defer teardown() - testAPIEdpoint := "/rest/api/2/project" - - raw, err := os.ReadFile("../testing/mock-data/all_projects.json") - if err != nil { - t.Error(err.Error()) - } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) - fmt.Fprint(w, string(raw)) - }) - - projects, _, err := testClient.Project.GetList(context.Background()) - if projects == nil { - t.Error("Expected project list. Project list is nil") - } - if err != nil { - t.Errorf("Error given: %s", err) - } -} - -func TestProjectService_ListWithOptions(t *testing.T) { +func TestProjectService_GetAll(t *testing.T) { setup() defer teardown() testAPIEdpoint := "/rest/api/2/project" @@ -47,7 +23,7 @@ func TestProjectService_ListWithOptions(t *testing.T) { fmt.Fprint(w, string(raw)) }) - projects, _, err := testClient.Project.ListWithOptions(context.Background(), &GetQueryOptions{Expand: "issueTypes"}) + projects, _, err := testClient.Project.GetAll(context.Background(), &GetQueryOptions{Expand: "issueTypes"}) if projects == nil { t.Error("Expected project list. Project list is nil") } diff --git a/onpremise/project.go b/onpremise/project.go index 364893f..cc03594 100644 --- a/onpremise/project.go +++ b/onpremise/project.go @@ -80,18 +80,11 @@ type PermissionScheme struct { Permissions []Permission `json:"permissions" structs:"permissions,omitempty"` } -// GetList gets all projects form Jira +// GetAll returns all projects form Jira with optional query params, like &GetQueryOptions{Expand: "issueTypes"} to get +// a list of all projects and their supported issuetypes. // -// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects -func (s *ProjectService) GetList(ctx context.Context) (*ProjectList, *Response, error) { - return s.ListWithOptions(ctx, &GetQueryOptions{}) -} - -// ListWithOptions gets all projects form Jira with optional query params, like &GetQueryOptions{Expand: "issueTypes"} to get -// a list of all projects and their supported issuetypes -// -// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects -func (s *ProjectService) ListWithOptions(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-projects/#api-rest-api-2-project-get +func (s *ProjectService) GetAll(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { apiEndpoint := "rest/api/2/project" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { diff --git a/onpremise/project_test.go b/onpremise/project_test.go index 2725d54..e997a07 100644 --- a/onpremise/project_test.go +++ b/onpremise/project_test.go @@ -8,31 +8,7 @@ import ( "testing" ) -func TestProjectService_GetList(t *testing.T) { - setup() - defer teardown() - testAPIEdpoint := "/rest/api/2/project" - - raw, err := os.ReadFile("../testing/mock-data/all_projects.json") - if err != nil { - t.Error(err.Error()) - } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) - fmt.Fprint(w, string(raw)) - }) - - projects, _, err := testClient.Project.GetList(context.Background()) - if projects == nil { - t.Error("Expected project list. Project list is nil") - } - if err != nil { - t.Errorf("Error given: %s", err) - } -} - -func TestProjectService_ListWithOptions(t *testing.T) { +func TestProjectService_GetAll(t *testing.T) { setup() defer teardown() testAPIEdpoint := "/rest/api/2/project" @@ -47,7 +23,7 @@ func TestProjectService_ListWithOptions(t *testing.T) { fmt.Fprint(w, string(raw)) }) - projects, _, err := testClient.Project.ListWithOptions(context.Background(), &GetQueryOptions{Expand: "issueTypes"}) + projects, _, err := testClient.Project.GetAll(context.Background(), &GetQueryOptions{Expand: "issueTypes"}) if projects == nil { t.Error("Expected project list. Project list is nil") } From 56386993ba03707120847a4325eb5c363cd1b4c9 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Mon, 12 Sep 2022 21:07:05 +0200 Subject: [PATCH 057/189] Add basic chapters like Installation and Supported Environments --- Makefile | 4 ++++ docs/developing.md | 10 ++++++++++ docs/installation.md | 15 +++++++++++++++ docs/supported-environments.md | 21 +++++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 docs/developing.md create mode 100644 docs/installation.md create mode 100644 docs/supported-environments.md diff --git a/Makefile b/Makefile index e9b063d..e1f5008 100644 --- a/Makefile +++ b/Makefile @@ -23,3 +23,7 @@ staticcheck: ## Runs static analysis to prevend bugs, foster code simplicity, pe .PHONY: all all: test vet fmt staticcheck ## Runs all source code quality targets (like test, vet, fmt, staticcheck) + +.PHONY: docs-serve +docs-serve: ## Runs the documentation development server (based on mkdocs) + mkdocs serve \ No newline at end of file diff --git a/docs/developing.md b/docs/developing.md new file mode 100644 index 0000000..39fd854 --- /dev/null +++ b/docs/developing.md @@ -0,0 +1,10 @@ +# Development + +## Running unit tests + +To run unit / example tests: + +```bash +cd $GOPATH/src/github.com/andygrunwald/go-jira +make test +``` diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..a45da29 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,15 @@ +# Installation + +## Requirements + +See the [list of supported environments] to validate that your setup is supported. + +## Installation + +It is go gettable + +```bash +go get github.com/andygrunwald/go-jira/v2 +``` + + [list of supported environments]: supported-environments.md \ No newline at end of file diff --git a/docs/supported-environments.md b/docs/supported-environments.md new file mode 100644 index 0000000..368ccaa --- /dev/null +++ b/docs/supported-environments.md @@ -0,0 +1,21 @@ +# Supported Environments + +## Go + +We follow the [Go Release Policy](https://go.dev/doc/devel/release#policy): + +> Each major Go release is supported until there are two newer major releases. For example, Go 1.5 was supported until the Go 1.7 release, and Go 1.6 was supported until the Go 1.8 release. We fix critical problems, including [critical security problems](https://go.dev/security), in supported releases as needed by issuing minor revisions (for example, Go 1.6.1, Go 1.6.2, and so on). + +## Jira + +### Jira Server (On-Premise solution) + +We follow the [Atlassian Support End of Life Policy](https://confluence.atlassian.com/support/atlassian-support-end-of-life-policy-201851003.html): + +> Atlassian supports feature versions for two years after the first major iteration of that version was released (for example, we support Jira Core 7.2.x for 2 years after Jira 7.2.0 was released). + +### Jira Cloud + +Officially, we support Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/) + +Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) is _currently_ not officially supported, because it is still in beta. \ No newline at end of file From 713c3780133f0a92bbdad67ce2405170307dc233 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Sep 2022 02:45:06 +0000 Subject: [PATCH 058/189] chore(deps): bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 460fa5b..d436253 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -8,7 +8,7 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-python@v2 with: python-version: 3.x From e67512615d9b637872443779db349c7b035ccea6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Sep 2022 05:15:19 +0000 Subject: [PATCH 059/189] chore(deps): bump actions/setup-python from 2 to 4 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2 to 4. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index d436253..7de518c 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: 3.x - run: pip install mkdocs-material From 4c62c7e3982afd4c8ce72874aa01d3cfba61cd2a Mon Sep 17 00:00:00 2001 From: Florian Kinder Date: Tue, 13 Sep 2022 16:38:03 +0200 Subject: [PATCH 060/189] Replaced ReadAll and Unmarshal with NewDecoder --- cloud/authentication.go | 12 +--- cloud/issue.go | 12 ++-- cloud/issue_test.go | 113 ++++++++++++++++++------------------ cloud/issuelinktype.go | 14 ++--- cloud/user.go | 14 ++--- cloud/version.go | 14 ++--- onpremise/authentication.go | 12 +--- onpremise/issue.go | 12 ++-- onpremise/issuelinktype.go | 14 ++--- onpremise/user.go | 14 ++--- onpremise/version.go | 14 ++--- 11 files changed, 96 insertions(+), 149 deletions(-) diff --git a/cloud/authentication.go b/cloud/authentication.go index 20d54bf..070a911 100644 --- a/cloud/authentication.go +++ b/cloud/authentication.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" ) @@ -172,16 +171,11 @@ func (s *AuthenticationService) GetCurrentUser(ctx context.Context) (*Session, e if resp.StatusCode != 200 { return nil, fmt.Errorf("getting user info failed with status : %d", resp.StatusCode) } - ret := new(Session) - data, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("couldn't read body from the response : %s", err) - } - - err = json.Unmarshal(data, &ret) + ret := new(Session) + err = json.NewDecoder(resp.Body).Decode(&ret) if err != nil { - return nil, fmt.Errorf("could not unmarshall received user info : %s", err) + return nil, err } return ret, nil diff --git a/cloud/issue.go b/cloud/issue.go index 99dc8f0..e25c522 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -785,22 +785,20 @@ func (s *IssueService) Create(ctx context.Context, issue *Issue) (*Issue, *Respo if err != nil { return nil, nil, err } + resp, err := s.client.Do(req, nil) if err != nil { // incase of error return the resp for further inspection return nil, resp, err } + defer resp.Body.Close() responseIssue := new(Issue) - defer resp.Body.Close() - data, err := io.ReadAll(resp.Body) + err = json.NewDecoder(resp.Body).Decode(&responseIssue) if err != nil { - return nil, resp, fmt.Errorf("could not read the returned data") - } - err = json.Unmarshal(data, responseIssue) - if err != nil { - return nil, resp, fmt.Errorf("could not unmarshall the data into struct") + return nil, resp, err } + return responseIssue, resp, nil } diff --git a/cloud/issue_test.go b/cloud/issue_test.go index bae570e..a8ad648 100644 --- a/cloud/issue_test.go +++ b/cloud/issue_test.go @@ -945,70 +945,69 @@ func TestIssueService_DoTransitionWithPayload(t *testing.T) { func TestIssueFields_TestMarshalJSON_PopulateUnknownsSuccess(t *testing.T) { data := `{ - "customfield_123":"test", - "description":"example bug report", - "project":{ - "self":"http://www.example.com/jira/rest/api/2/project/EX", + "customfield_123":"test", + "description":"example bug report", + "project":{ + "self":"http://www.example.com/jira/rest/api/2/project/EX", + "id":"10000", + "key":"EX", + "name":"Example", + "avatarUrls":{ + "48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000", + "24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000", + "16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000", + "32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000" + }, + "projectCategory":{ + "self":"http://www.example.com/jira/rest/api/2/projectCategory/10000", + "id":"10000", + "name":"FIRST", + "description":"First Project Category" + } + }, + "issuelinks":[ + { + "id":"10001", + "type":{ "id":"10000", - "key":"EX", - "name":"Example", - "avatarUrls":{ - "48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000", - "24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000", - "16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000", - "32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000" + "name":"Dependent", + "inward":"depends on", + "outward":"is depended by" }, - "projectCategory":{ - "self":"http://www.example.com/jira/rest/api/2/projectCategory/10000", - "id":"10000", - "name":"FIRST", - "description":"First Project Category" + "outwardIssue":{ + "id":"10004L", + "key":"PRJ-2", + "self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2", + "fields":{ + "status":{ + "iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png", + "name":"Open" + } + } } }, - "issuelinks":[ - { - "id":"10001", - "type":{ - "id":"10000", - "name":"Dependent", - "inward":"depends on", - "outward":"is depended by" - }, - "outwardIssue":{ - "id":"10004L", - "key":"PRJ-2", - "self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2", - "fields":{ - "status":{ - "iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png", - "name":"Open" - } - } - } + { + "id":"10002", + "type":{ + "id":"10000", + "name":"Dependent", + "inward":"depends on", + "outward":"is depended by" }, - { - "id":"10002", - "type":{ - "id":"10000", - "name":"Dependent", - "inward":"depends on", - "outward":"is depended by" - }, - "inwardIssue":{ - "id":"10004", - "key":"PRJ-3", - "self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3", - "fields":{ - "status":{ - "iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png", - "name":"Open" - } - } + "inwardIssue":{ + "id":"10004", + "key":"PRJ-3", + "self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3", + "fields":{ + "status":{ + "iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png", + "name":"Open" } } - ] - - }` + } + } + ] + }` i := new(IssueFields) err := json.Unmarshal([]byte(data), i) diff --git a/cloud/issuelinktype.go b/cloud/issuelinktype.go index 5664a7b..c228f06 100644 --- a/cloud/issuelinktype.go +++ b/cloud/issuelinktype.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" ) @@ -63,19 +62,14 @@ func (s *IssueLinkTypeService) Create(ctx context.Context, linkType *IssueLinkTy if err != nil { return nil, resp, err } + defer resp.Body.Close() responseLinkType := new(IssueLinkType) - defer resp.Body.Close() - data, err := io.ReadAll(resp.Body) - if err != nil { - e := fmt.Errorf("could not read the returned data") - return nil, resp, NewJiraError(resp, e) - } - err = json.Unmarshal(data, responseLinkType) + err = json.NewDecoder(resp.Body).Decode(&responseLinkType) if err != nil { - e := fmt.Errorf("could no unmarshal the data into struct") - return nil, resp, NewJiraError(resp, e) + return nil, resp, err } + return linkType, resp, nil } diff --git a/cloud/user.go b/cloud/user.go index cc397cc..5bce9e3 100644 --- a/cloud/user.go +++ b/cloud/user.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" ) @@ -96,19 +95,14 @@ func (s *UserService) Create(ctx context.Context, user *User) (*User, *Response, if err != nil { return nil, resp, err } + defer resp.Body.Close() responseUser := new(User) - defer resp.Body.Close() - data, err := io.ReadAll(resp.Body) - if err != nil { - e := fmt.Errorf("could not read the returned data") - return nil, resp, NewJiraError(resp, e) - } - err = json.Unmarshal(data, responseUser) + err = json.NewDecoder(resp.Body).Decode(&responseUser) if err != nil { - e := fmt.Errorf("could not unmarshall the data into struct") - return nil, resp, NewJiraError(resp, e) + return nil, resp, err } + return responseUser, resp, nil } diff --git a/cloud/version.go b/cloud/version.go index b5780c6..4c1e1af 100644 --- a/cloud/version.go +++ b/cloud/version.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" ) @@ -59,19 +58,14 @@ func (s *VersionService) Create(ctx context.Context, version *Version) (*Version if err != nil { return nil, resp, err } + defer resp.Body.Close() responseVersion := new(Version) - defer resp.Body.Close() - data, err := io.ReadAll(resp.Body) - if err != nil { - e := fmt.Errorf("could not read the returned data") - return nil, resp, NewJiraError(resp, e) - } - err = json.Unmarshal(data, responseVersion) + err = json.NewDecoder(resp.Body).Decode(&responseVersion) if err != nil { - e := fmt.Errorf("could not unmarshall the data into struct") - return nil, resp, NewJiraError(resp, e) + return nil, resp, err } + return responseVersion, resp, nil } diff --git a/onpremise/authentication.go b/onpremise/authentication.go index a08cdb8..27bc8c4 100644 --- a/onpremise/authentication.go +++ b/onpremise/authentication.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" ) @@ -172,16 +171,11 @@ func (s *AuthenticationService) GetCurrentUser(ctx context.Context) (*Session, e if resp.StatusCode != 200 { return nil, fmt.Errorf("getting user info failed with status : %d", resp.StatusCode) } - ret := new(Session) - data, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("couldn't read body from the response : %s", err) - } - - err = json.Unmarshal(data, &ret) + ret := new(Session) + err = json.NewDecoder(resp.Body).Decode(&ret) if err != nil { - return nil, fmt.Errorf("could not unmarshall received user info : %s", err) + return nil, err } return ret, nil diff --git a/onpremise/issue.go b/onpremise/issue.go index b549afe..a8828be 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -785,22 +785,20 @@ func (s *IssueService) Create(ctx context.Context, issue *Issue) (*Issue, *Respo if err != nil { return nil, nil, err } + resp, err := s.client.Do(req, nil) if err != nil { // incase of error return the resp for further inspection return nil, resp, err } + defer resp.Body.Close() responseIssue := new(Issue) - defer resp.Body.Close() - data, err := io.ReadAll(resp.Body) + err = json.NewDecoder(resp.Body).Decode(&responseIssue) if err != nil { - return nil, resp, fmt.Errorf("could not read the returned data") - } - err = json.Unmarshal(data, responseIssue) - if err != nil { - return nil, resp, fmt.Errorf("could not unmarshall the data into struct") + return nil, resp, err } + return responseIssue, resp, nil } diff --git a/onpremise/issuelinktype.go b/onpremise/issuelinktype.go index 5599084..2177363 100644 --- a/onpremise/issuelinktype.go +++ b/onpremise/issuelinktype.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" ) @@ -63,19 +62,14 @@ func (s *IssueLinkTypeService) Create(ctx context.Context, linkType *IssueLinkTy if err != nil { return nil, resp, err } + defer resp.Body.Close() responseLinkType := new(IssueLinkType) - defer resp.Body.Close() - data, err := io.ReadAll(resp.Body) - if err != nil { - e := fmt.Errorf("could not read the returned data") - return nil, resp, NewJiraError(resp, e) - } - err = json.Unmarshal(data, responseLinkType) + err = json.NewDecoder(resp.Body).Decode(&responseLinkType) if err != nil { - e := fmt.Errorf("could no unmarshal the data into struct") - return nil, resp, NewJiraError(resp, e) + return nil, resp, err } + return linkType, resp, nil } diff --git a/onpremise/user.go b/onpremise/user.go index a2a8c7f..b6205de 100644 --- a/onpremise/user.go +++ b/onpremise/user.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" ) @@ -96,19 +95,14 @@ func (s *UserService) Create(ctx context.Context, user *User) (*User, *Response, if err != nil { return nil, resp, err } + defer resp.Body.Close() responseUser := new(User) - defer resp.Body.Close() - data, err := io.ReadAll(resp.Body) - if err != nil { - e := fmt.Errorf("could not read the returned data") - return nil, resp, NewJiraError(resp, e) - } - err = json.Unmarshal(data, responseUser) + err = json.NewDecoder(resp.Body).Decode(&responseUser) if err != nil { - e := fmt.Errorf("could not unmarshall the data into struct") - return nil, resp, NewJiraError(resp, e) + return nil, resp, err } + return responseUser, resp, nil } diff --git a/onpremise/version.go b/onpremise/version.go index 697cb51..69e4bec 100644 --- a/onpremise/version.go +++ b/onpremise/version.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" ) @@ -59,19 +58,14 @@ func (s *VersionService) Create(ctx context.Context, version *Version) (*Version if err != nil { return nil, resp, err } + defer resp.Body.Close() responseVersion := new(Version) - defer resp.Body.Close() - data, err := io.ReadAll(resp.Body) - if err != nil { - e := fmt.Errorf("could not read the returned data") - return nil, resp, NewJiraError(resp, e) - } - err = json.Unmarshal(data, responseVersion) + err = json.NewDecoder(resp.Body).Decode(&responseVersion) if err != nil { - e := fmt.Errorf("could not unmarshall the data into struct") - return nil, resp, NewJiraError(resp, e) + return nil, resp, err } + return responseVersion, resp, nil } From 2b3e37bc15293db4d0e96fdb1ec99d39aaed6a53 Mon Sep 17 00:00:00 2001 From: Florian Kinder Date: Tue, 13 Sep 2022 17:02:43 +0200 Subject: [PATCH 061/189] do not hide errors --- cloud/authentication.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/cloud/authentication.go b/cloud/authentication.go index 070a911..e730209 100644 --- a/cloud/authentication.go +++ b/cloud/authentication.go @@ -73,17 +73,16 @@ func (s *AuthenticationService) AcquireSessionCookie(ctx context.Context, userna session := new(Session) resp, err := s.client.Do(req, session) - - if resp != nil { - session.Cookies = resp.Cookies() - } - if err != nil { - return false, fmt.Errorf("auth at Jira instance failed (HTTP(S) request). %s", err) + return false, fmt.Errorf("auth at Jira instance failed (HTTP(S) request). %w", err) } + if resp != nil && resp.StatusCode != 200 { return false, fmt.Errorf("auth at Jira instance failed (HTTP(S) request). Status code: %d", resp.StatusCode) } + if resp != nil { + session.Cookies = resp.Cookies() + } s.client.session = session s.authType = authTypeSession @@ -127,12 +126,12 @@ func (s *AuthenticationService) Logout(ctx context.Context) error { apiEndpoint := "rest/auth/1/session" req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { - return fmt.Errorf("creating the request to log the user out failed : %s", err) + return fmt.Errorf("creating the request to log the user out failed : %w", err) } resp, err := s.client.Do(req, nil) if err != nil { - return fmt.Errorf("error sending the logout request: %s", err) + return fmt.Errorf("error sending the logout request: %w", err) } defer resp.Body.Close() if resp.StatusCode != 204 { @@ -160,12 +159,12 @@ func (s *AuthenticationService) GetCurrentUser(ctx context.Context) (*Session, e apiEndpoint := "rest/auth/1/session" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { - return nil, fmt.Errorf("could not create request for getting user info : %s", err) + return nil, fmt.Errorf("could not create request for getting user info: %w", err) } resp, err := s.client.Do(req, nil) if err != nil { - return nil, fmt.Errorf("error sending request to get user info : %s", err) + return nil, fmt.Errorf("error sending request to get user info: %w", err) } defer resp.Body.Close() if resp.StatusCode != 200 { From 3b6e9df9c9d95e5ea991cd09dd79a10fa2178bd5 Mon Sep 17 00:00:00 2001 From: Maciej Pijanowski Date: Mon, 26 Sep 2022 13:52:29 +0200 Subject: [PATCH 062/189] README.md: fix example URLs Signed-off-by: Maciej Pijanowski --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4c4dd77..925860a 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ For convenience, capability for basic and cookie-based authentication is include Token-based authentication uses the basic authentication scheme, with a user-generated API token in place of a user's password. You can generate a token for your user [here](https://id.atlassian.com/manage-profile/security/api-tokens). Additional information about Atlassian Cloud API tokens can be found [here](https://confluence.atlassian.com/cloud/api-tokens-938839638.html). -A more thorough, [runnable example](examples/basicauth/main.go) is provided in the examples directory. +A more thorough, [runnable example](cloud/examples/basicauth/main.go) is provided in the examples directory. ```go func main() { @@ -244,10 +244,7 @@ func main() { Jira API has limit on maxResults it can return. You may have a usecase where you need to get all issues for given JQL. This example shows reference implementation of GetAllIssues function which does pagination on Jira API to get all the issues for given JQL -please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/master/examples/pagination/main.go) - - - +please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/master/cloud/examples/pagination/main.go) ### Call a not implemented API endpoint From f00640c2fcc11a01433c3f90b06cafc38c79e730 Mon Sep 17 00:00:00 2001 From: Carlos Treminio Date: Sun, 9 Oct 2022 21:00:15 -0600 Subject: [PATCH 063/189] :recycle: IssueCategory Service reviewed 1. The Get() method has been created on the cloud and onPremise versions. 2. The JSON schema for the Cloud and OnPremise products are the same. 3. The Test Cases have been created in both modules. 4. Created an example of the new implementation 5. resolves #552 6. resolved #530 --- cloud/examples/getthestatuscategories/main.go | 30 +++++++++++++ cloud/statuscategory.go | 44 ++++++++++++++++--- cloud/statuscategory_test.go | 30 +++++++++++-- .../examples/getthestatuscategories/main.go | 30 +++++++++++++ onpremise/statuscategory.go | 44 ++++++++++++++++--- onpremise/statuscategory_test.go | 24 ++++++++++ testing/mock-data/status_category.json | 7 +++ 7 files changed, 194 insertions(+), 15 deletions(-) create mode 100644 cloud/examples/getthestatuscategories/main.go create mode 100644 onpremise/examples/getthestatuscategories/main.go create mode 100644 testing/mock-data/status_category.json diff --git a/cloud/examples/getthestatuscategories/main.go b/cloud/examples/getthestatuscategories/main.go new file mode 100644 index 0000000..02c343d --- /dev/null +++ b/cloud/examples/getthestatuscategories/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + jira "github.com/andygrunwald/go-jira/v2/cloud" + "log" +) + +func main() { + + jiraClient, _ := jira.NewClient("https://mattermost.atlassian.net/", nil) + + categories, resp, err := jiraClient.StatusCategory.GetList(context.TODO()) + if err != nil { + log.Println(resp.StatusCode) + panic(err) + } + + for _, statusCategory := range categories { + log.Println(statusCategory) + } + + category, resp, err := jiraClient.StatusCategory.Get(context.TODO(), "1") + if err != nil { + log.Println(resp.StatusCode) + panic(err) + } + + log.Println(category) +} diff --git a/cloud/statuscategory.go b/cloud/statuscategory.go index 74dc4c7..a1e0439 100644 --- a/cloud/statuscategory.go +++ b/cloud/statuscategory.go @@ -2,12 +2,18 @@ package cloud import ( "context" + "errors" + "fmt" "net/http" ) // StatusCategoryService handles status categories for the Jira instance / API. // -// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Statuscategory +// Use it to obtain a list of all status categories and the details of a category. +// +// Status categories provided a mechanism for categorizing statuses. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-group-workflow-status-categories type StatusCategoryService service // StatusCategory represents the category a status belongs to. @@ -30,18 +36,44 @@ const ( // GetList gets all status categories from Jira // -// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-get func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, *Response, error) { - apiEndpoint := "rest/api/2/statuscategory" + apiEndpoint := "/rest/api/3/statuscategory" + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + var statusCategories []StatusCategory + resp, err := s.client.Do(req, &statusCategories) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return statusCategories, resp, nil +} + +// Get returns a status category. +// +// Status categories provided a mechanism for categorizing statuses. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-idorkey-get +func (s *StatusCategoryService) Get(ctx context.Context, statusCategoryID string) (*StatusCategory, *Response, error) { + + if statusCategoryID == "" { + return nil, nil, errors.New("jira: not status category set") + } + + apiEndpoint := fmt.Sprintf("/rest/api/3/statuscategory/%v", statusCategoryID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } - statusCategoryList := []StatusCategory{} - resp, err := s.client.Do(req, &statusCategoryList) + statusCategory := new(StatusCategory) + resp, err := s.client.Do(req, statusCategory) if err != nil { return nil, resp, NewJiraError(resp, err) } - return statusCategoryList, resp, nil + + return statusCategory, resp, nil } diff --git a/cloud/statuscategory_test.go b/cloud/statuscategory_test.go index f05705a..6d1b6dc 100644 --- a/cloud/statuscategory_test.go +++ b/cloud/statuscategory_test.go @@ -11,15 +11,15 @@ import ( func TestStatusCategoryService_GetList(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/statuscategory" + testAPIEndpoint := "/rest/api/3/statuscategory" raw, err := os.ReadFile("../testing/mock-data/all_statuscategories.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -31,3 +31,27 @@ func TestStatusCategoryService_GetList(t *testing.T) { t.Errorf("Error given: %s", err) } } + +func TestStatusCategoryService_Get(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/3/statuscategory/1" + + raw, err := os.ReadFile("../testing/mock-data/status_category.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + statusCategory, _, err := testClient.StatusCategory.Get(context.Background(), "1") + + if err != nil { + t.Errorf("Error given: %s", err) + } else if statusCategory == nil { + t.Error("Expected status category. StatusCategory is nil") + } +} diff --git a/onpremise/examples/getthestatuscategories/main.go b/onpremise/examples/getthestatuscategories/main.go new file mode 100644 index 0000000..a0a6565 --- /dev/null +++ b/onpremise/examples/getthestatuscategories/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + jira "github.com/andygrunwald/go-jira/v2/onpremise" + "log" +) + +func main() { + + jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", nil) + + categories, resp, err := jiraClient.StatusCategory.GetList(context.TODO()) + if err != nil { + log.Println(resp.StatusCode) + panic(err) + } + + for _, statusCategory := range categories { + log.Println(statusCategory) + } + + category, resp, err := jiraClient.StatusCategory.Get(context.TODO(), "1") + if err != nil { + log.Println(resp.StatusCode) + panic(err) + } + + log.Println(category) +} diff --git a/onpremise/statuscategory.go b/onpremise/statuscategory.go index 3178c63..bc8c84f 100644 --- a/onpremise/statuscategory.go +++ b/onpremise/statuscategory.go @@ -2,12 +2,18 @@ package onpremise import ( "context" + "errors" + "fmt" "net/http" ) // StatusCategoryService handles status categories for the Jira instance / API. // -// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Statuscategory +// Use it to obtain a list of all status categories and the details of a category. +// +// Status categories provided a mechanism for categorizing statuses. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-workflow-status-categories/#api-group-workflow-status-categories type StatusCategoryService service // StatusCategory represents the category a status belongs to. @@ -30,18 +36,44 @@ const ( // GetList gets all status categories from Jira // -// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-workflow-status-categories/#api-rest-api-2-statuscategory-get func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, *Response, error) { - apiEndpoint := "rest/api/2/statuscategory" + apiEndpoint := "/rest/api/2/statuscategory" + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + var statusCategories []StatusCategory + resp, err := s.client.Do(req, &statusCategories) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return statusCategories, resp, nil +} + +// Get returns a status category. +// +// Status categories provided a mechanism for categorizing statuses. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-workflow-status-categories/#api-rest-api-2-statuscategory-idorkey-get +func (s *StatusCategoryService) Get(ctx context.Context, statusCategoryID string) (*StatusCategory, *Response, error) { + + if statusCategoryID == "" { + return nil, nil, errors.New("jira: not status category set") + } + + apiEndpoint := fmt.Sprintf("/rest/api/2/statuscategory/%v", statusCategoryID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } - statusCategoryList := []StatusCategory{} - resp, err := s.client.Do(req, &statusCategoryList) + statusCategory := new(StatusCategory) + resp, err := s.client.Do(req, statusCategory) if err != nil { return nil, resp, NewJiraError(resp, err) } - return statusCategoryList, resp, nil + + return statusCategory, resp, nil } diff --git a/onpremise/statuscategory_test.go b/onpremise/statuscategory_test.go index 93af9d7..cc2b9dc 100644 --- a/onpremise/statuscategory_test.go +++ b/onpremise/statuscategory_test.go @@ -31,3 +31,27 @@ func TestStatusCategoryService_GetList(t *testing.T) { t.Errorf("Error given: %s", err) } } + +func TestStatusCategoryService_Get(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/2/statuscategory/1" + + raw, err := os.ReadFile("../testing/mock-data/status_category.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + statusCategory, _, err := testClient.StatusCategory.Get(context.Background(), "1") + + if err != nil { + t.Errorf("Error given: %s", err) + } else if statusCategory == nil { + t.Error("Expected status category. StatusCategory is nil") + } +} diff --git a/testing/mock-data/status_category.json b/testing/mock-data/status_category.json new file mode 100644 index 0000000..6302477 --- /dev/null +++ b/testing/mock-data/status_category.json @@ -0,0 +1,7 @@ +{ + "self": "https://issues.apache.org/jira/rest/api/2/resolution/1", + "id": 1, + "key": "undefined", + "colorName": "medium-gray", + "name": "No Category" +} \ No newline at end of file From 2113d5cdabf425a23188c1c64b1753b67c007095 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 09:55:19 +0200 Subject: [PATCH 064/189] Fix all URLs in the README This is a follow up from #576 --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 925860a..5a5429e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # go-jira -[![GoDoc](https://godoc.org/github.com/andygrunwald/go-jira?status.svg)](https://godoc.org/github.com/andygrunwald/go-jira) +[![GoDoc](https://pkg.go.dev/badge/github.com/andygrunwald/go-jira?utm_source=godoc)](https://pkg.go.dev/github.com/andygrunwald/go-jira) [![Build Status](https://github.com/andygrunwald/go-jira/actions/workflows/testing.yml/badge.svg)](https://github.com/andygrunwald/go-jira/actions/workflows/testing.yml) -[![Go Report Card](https://goreportcard.com/badge/github.com/andygrunwald/go-jira)](https://goreportcard.com/report/github.com/andygrunwald/go-jira) +[![Go Report Card](https://img.shields.io/badge/go%20report-A+-brightgreen.svg?style=flat)](https://goreportcard.com/report/github.com/andygrunwald/go-jira) -[Go](https://golang.org/) client library for [Atlassian Jira](https://www.atlassian.com/software/jira). +[Go](https://go.dev/) client library for [Atlassian Jira](https://www.atlassian.com/software/jira). ![Go client library for Atlassian Jira](./img/logo_small.png "Go client library for Atlassian Jira.") @@ -34,7 +34,7 @@ Latest stable release: [v1.16.0](https://github.com/andygrunwald/go-jira/release * Create and retrieve issue transitions (status updates) * Call every API endpoint of the Jira, even if it is not directly implemented in this library -This package is not Jira API complete (yet), but you can call every API endpoint you want. See [Call a not implemented API endpoint](#call-a-not-implemented-api-endpoint) how to do this. For all possible API endpoints of Jira have a look at [latest Jira REST API documentation](https://docs.atlassian.com/jira/REST/latest/). +This package is not Jira API complete (yet), but you can call every API endpoint you want. See [Call a not implemented API endpoint](#call-a-not-implemented-api-endpoint) how to do this. For all possible API endpoints of Jira have a look at [latest Jira REST API documentation](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/). ## Requirements @@ -72,14 +72,14 @@ go test -v ./... ## API -Please have a look at the [GoDoc documentation](https://godoc.org/github.com/andygrunwald/go-jira) for a detailed API description. +Please have a look at the [GoDoc documentation](https://pkg.go.dev/github.com/andygrunwald/go-jira) for a detailed API description. -The [latest Jira REST API documentation](https://docs.atlassian.com/jira/REST/latest/) was the base document for this package. +The [latest Jira REST API documentation](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) was the base document for this package. ## Examples Further a few examples how the API can be used. -A few more examples are available in the [GoDoc examples section](https://godoc.org/github.com/andygrunwald/go-jira#pkg-examples). +A few more examples are available in the [GoDoc examples section](https://pkg.go.dev/github.com/andygrunwald/go-jira#section-directories). ### Get a single issue @@ -116,7 +116,7 @@ For convenience, capability for basic and cookie-based authentication is include #### Token (Jira on Atlassian Cloud) -Token-based authentication uses the basic authentication scheme, with a user-generated API token in place of a user's password. You can generate a token for your user [here](https://id.atlassian.com/manage-profile/security/api-tokens). Additional information about Atlassian Cloud API tokens can be found [here](https://confluence.atlassian.com/cloud/api-tokens-938839638.html). +Token-based authentication uses the basic authentication scheme, with a user-generated API token in place of a user's password. You can generate a token for your user [here](https://id.atlassian.com/manage-profile/security/api-tokens). Additional information about Atlassian Cloud API tokens can be found [here](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/). A more thorough, [runnable example](cloud/examples/basicauth/main.go) is provided in the examples directory. @@ -244,7 +244,7 @@ func main() { Jira API has limit on maxResults it can return. You may have a usecase where you need to get all issues for given JQL. This example shows reference implementation of GetAllIssues function which does pagination on Jira API to get all the issues for given JQL -please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/master/cloud/examples/pagination/main.go) +please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/main/cloud/examples/pagination/main.go) ### Call a not implemented API endpoint @@ -315,13 +315,13 @@ A few examples: * Implement a new feature or endpoint * Sharing the love of [go-jira](https://github.com/andygrunwald/go-jira) and help people to get use to it -If you are new to pull requests, checkout [Collaborating on projects using issues and pull requests / Creating a pull request](https://help.github.com/articles/creating-a-pull-request/). +If you are new to pull requests, checkout [Collaborating on projects using issues and pull requests / Creating a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request). ### Supported Go versions We follow the [Go Release Policy](https://go.dev/doc/devel/release#policy): -> Each major Go release is supported until there are two newer major releases. For example, Go 1.5 was supported until the Go 1.7 release, and Go 1.6 was supported until the Go 1.8 release. We fix critical problems, including [critical security problems](https://go.dev/security), in supported releases as needed by issuing minor revisions (for example, Go 1.6.1, Go 1.6.2, and so on). +> Each major Go release is supported until there are two newer major releases. For example, Go 1.5 was supported until the Go 1.7 release, and Go 1.6 was supported until the Go 1.8 release. We fix critical problems, including [critical security problems](https://go.dev/security/), in supported releases as needed by issuing minor revisions (for example, Go 1.6.1, Go 1.6.2, and so on). ### Supported Jira versions @@ -333,21 +333,21 @@ We follow the [Atlassian Support End of Life Policy](https://confluence.atlassia #### Jira Cloud -* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/) +* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/intro/) Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) is _currently_ not officially supported, because it is still in beta. ### Official Jira API documentation * [Jira Server (On-Premise solution)](https://developer.atlassian.com/server/jira/platform/rest-apis/) -* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/) +* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/intro/) * Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) ### Sandbox environment for testing Jira offers sandbox test environments at http://go.atlassian.com/cloud-dev. -You can read more about them at https://developer.atlassian.com/blog/2016/04/cloud-ecosystem-dev-env/. +You can read more about them at https://blog.developer.atlassian.com/cloud-ecosystem-dev-env/. ## Releasing From eb8b02d335af3528284f845d25c98293691d6c52 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 09:57:05 +0200 Subject: [PATCH 065/189] README: Removed the installation part for gopkg.in --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index 5a5429e..d001aaa 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,6 @@ It is go gettable go get github.com/andygrunwald/go-jira ``` -For stable versions you can use one of our tags with [gopkg.in](http://labix.org/gopkg.in). E.g. - -```go -package main - -import ( - jira "gopkg.in/andygrunwald/go-jira.v1" -) -... -``` - (optional) to run unit / example tests: ```bash From 0550807b6fa3a706c0a29ec475afe7fc8e1028d2 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:01:01 +0200 Subject: [PATCH 066/189] Makefile: Switch to latest version of staticcheck --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e1f5008..48a693a 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ fmt: ## Runs go fmt (to check for go coding guidelines). .PHONY: staticcheck staticcheck: ## Runs static analysis to prevend bugs, foster code simplicity, performance and editor integration. - go install honnef.co/go/tools/cmd/staticcheck@2022.1 + go install honnef.co/go/tools/cmd/staticcheck@latest staticcheck ./... .PHONY: all From 959dd7ee4d243129324db386413815a580e00f93 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:01:14 +0200 Subject: [PATCH 067/189] Makefile: Add commands for testing coverage --- .gitignore | 5 ++++- Makefile | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e200606..9d26f47 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,7 @@ _testmain.go *.prof *.iml .idea -.DS_Store \ No newline at end of file +.DS_Store + +# Code coverage +coverage.txt \ No newline at end of file diff --git a/Makefile b/Makefile index 48a693a..2ff82e3 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,15 @@ help: ## Outputs the help. .PHONY: test test: ## Runs all unit, integration and example tests. - go test -race -v ./... + go test -v -race ./... + +.PHONY: test-coverage +test-coverage: ## Runs all unit tests + gathers code coverage + go test -v -race -coverprofile coverage.txt ./... + +.PHONY: test-coverage-html +test-coverage-html: test-coverage ## Runs all unit tests + gathers code coverage + displays them in your default browser + go tool cover -html=coverage.txt .PHONY: vet vet: ## Runs go vet (to detect suspicious constructs). From fc5561d1e16583dab1969402f8ef40e21aa945a2 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:02:58 +0200 Subject: [PATCH 068/189] README: Add chapter about executing unit tests --- README.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d001aaa..108a5e9 100644 --- a/README.md +++ b/README.md @@ -48,17 +48,10 @@ of Go are officially supported. It is go gettable -```bash +```sh go get github.com/andygrunwald/go-jira ``` -(optional) to run unit / example tests: - -```bash -cd $GOPATH/src/github.com/andygrunwald/go-jira -go test -v ./... -``` - ## API Please have a look at the [GoDoc documentation](https://pkg.go.dev/github.com/andygrunwald/go-jira) for a detailed API description. @@ -282,7 +275,9 @@ func main() { * [andygrunwald/jitic](https://github.com/andygrunwald/jitic) - The Jira Ticket Checker -## Code structure +## Development + +### Code structure The code structure of this package was inspired by [google/go-github](https://github.com/google/go-github). @@ -290,6 +285,20 @@ There is one main part (the client). Based on this main client the other endpoints, like Issues or Authentication are extracted in services. E.g. `IssueService` or `AuthenticationService`. These services own a responsibility of the single endpoints / usecases of Jira. +### Unit testing + +To run the local unit tests, execute + +```sh +$ make test +``` + +To run the local unit tests and view the unit test code coverage in your local web browser, execute + +```sh +$ make test-coverage-html +``` + ## Contribution We ❤️ PR's From 1df2c280b947b997834bb78aec2ffa7ed15dd9ef Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:22:38 +0200 Subject: [PATCH 069/189] Updated changelog with latest changes --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d935f..a6f10d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -344,9 +344,14 @@ client.Project.GetAll(ctx, &GetQueryOptions{}) * UserAgent: Client HTTP calls are now identifable via a User Agent. This user agent can be configured (default: `go-jira/2.0.0`) * The underlying used HTTP client for API calls can be retrieved via `client.Client()` +### Bug Fixes + +* README: Fixed all (broken) links + ### Other * Replace all "GET", "POST", ... with http.MethodGet (and related) constants +* Development: Added `make` commands to collect (unit) test coverage ### Changes From 6267520d0ed8dbc83292357179715e7644903ee2 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:23:01 +0200 Subject: [PATCH 070/189] PersonalAccessToken Auth: Used fmt.Sprintf to concat a string --- cloud/auth_transport_personal_access_token.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cloud/auth_transport_personal_access_token.go b/cloud/auth_transport_personal_access_token.go index fb2f339..2661ac0 100644 --- a/cloud/auth_transport_personal_access_token.go +++ b/cloud/auth_transport_personal_access_token.go @@ -1,6 +1,9 @@ package cloud -import "net/http" +import ( + "fmt" + "net/http" +) // PATAuthTransport is an http.RoundTripper that authenticates all requests // using the Personal Access Token specified. @@ -18,7 +21,8 @@ type PATAuthTransport struct { // basic auth and return the RoundTripper for this transport type. func (t *PATAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { req2 := cloneRequest(req) // per RoundTripper contract - req2.Header.Set("Authorization", "Bearer "+t.Token) + + req2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.Token)) return t.transport().RoundTrip(req2) } From 2565daecd1a9f6e259fd102b70169c5739245a87 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:29:42 +0200 Subject: [PATCH 071/189] README: API-Version: Official support for Jira Cloud API in version 3 --- CHANGELOG.md | 1 + README.md | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6f10d9..4f84c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -343,6 +343,7 @@ client.Project.GetAll(ctx, &GetQueryOptions{}) * UserAgent: Client HTTP calls are now identifable via a User Agent. This user agent can be configured (default: `go-jira/2.0.0`) * The underlying used HTTP client for API calls can be retrieved via `client.Client()` +* API-Version: Official support for Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) ### Bug Fixes diff --git a/README.md b/README.md index 108a5e9..c30f564 100644 --- a/README.md +++ b/README.md @@ -331,9 +331,11 @@ We follow the [Atlassian Support End of Life Policy](https://confluence.atlassia #### Jira Cloud -* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/intro/) +We support Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/). +Even if this API version is _currently_ in beta (by Atlassian): -Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) is _currently_ not officially supported, because it is still in beta. +[Version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/) and version 3 of the API offer the same collection of operations. +However, version 3 provides support for the [Atlassian Document Format (ADF)](https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/) in a subset of the API. ### Official Jira API documentation From 89295836f65612582eb00905543036b18e07d086 Mon Sep 17 00:00:00 2001 From: Maciej Pijanowski Date: Mon, 26 Sep 2022 13:52:29 +0200 Subject: [PATCH 072/189] README.md: fix example URLs Signed-off-by: Maciej Pijanowski --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4c4dd77..925860a 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ For convenience, capability for basic and cookie-based authentication is include Token-based authentication uses the basic authentication scheme, with a user-generated API token in place of a user's password. You can generate a token for your user [here](https://id.atlassian.com/manage-profile/security/api-tokens). Additional information about Atlassian Cloud API tokens can be found [here](https://confluence.atlassian.com/cloud/api-tokens-938839638.html). -A more thorough, [runnable example](examples/basicauth/main.go) is provided in the examples directory. +A more thorough, [runnable example](cloud/examples/basicauth/main.go) is provided in the examples directory. ```go func main() { @@ -244,10 +244,7 @@ func main() { Jira API has limit on maxResults it can return. You may have a usecase where you need to get all issues for given JQL. This example shows reference implementation of GetAllIssues function which does pagination on Jira API to get all the issues for given JQL -please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/master/examples/pagination/main.go) - - - +please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/master/cloud/examples/pagination/main.go) ### Call a not implemented API endpoint From bdbbc43e4e8a75cd0f4ce7a2a765e73ac6a1ed78 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 09:55:19 +0200 Subject: [PATCH 073/189] Fix all URLs in the README This is a follow up from #576 --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 925860a..5a5429e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # go-jira -[![GoDoc](https://godoc.org/github.com/andygrunwald/go-jira?status.svg)](https://godoc.org/github.com/andygrunwald/go-jira) +[![GoDoc](https://pkg.go.dev/badge/github.com/andygrunwald/go-jira?utm_source=godoc)](https://pkg.go.dev/github.com/andygrunwald/go-jira) [![Build Status](https://github.com/andygrunwald/go-jira/actions/workflows/testing.yml/badge.svg)](https://github.com/andygrunwald/go-jira/actions/workflows/testing.yml) -[![Go Report Card](https://goreportcard.com/badge/github.com/andygrunwald/go-jira)](https://goreportcard.com/report/github.com/andygrunwald/go-jira) +[![Go Report Card](https://img.shields.io/badge/go%20report-A+-brightgreen.svg?style=flat)](https://goreportcard.com/report/github.com/andygrunwald/go-jira) -[Go](https://golang.org/) client library for [Atlassian Jira](https://www.atlassian.com/software/jira). +[Go](https://go.dev/) client library for [Atlassian Jira](https://www.atlassian.com/software/jira). ![Go client library for Atlassian Jira](./img/logo_small.png "Go client library for Atlassian Jira.") @@ -34,7 +34,7 @@ Latest stable release: [v1.16.0](https://github.com/andygrunwald/go-jira/release * Create and retrieve issue transitions (status updates) * Call every API endpoint of the Jira, even if it is not directly implemented in this library -This package is not Jira API complete (yet), but you can call every API endpoint you want. See [Call a not implemented API endpoint](#call-a-not-implemented-api-endpoint) how to do this. For all possible API endpoints of Jira have a look at [latest Jira REST API documentation](https://docs.atlassian.com/jira/REST/latest/). +This package is not Jira API complete (yet), but you can call every API endpoint you want. See [Call a not implemented API endpoint](#call-a-not-implemented-api-endpoint) how to do this. For all possible API endpoints of Jira have a look at [latest Jira REST API documentation](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/). ## Requirements @@ -72,14 +72,14 @@ go test -v ./... ## API -Please have a look at the [GoDoc documentation](https://godoc.org/github.com/andygrunwald/go-jira) for a detailed API description. +Please have a look at the [GoDoc documentation](https://pkg.go.dev/github.com/andygrunwald/go-jira) for a detailed API description. -The [latest Jira REST API documentation](https://docs.atlassian.com/jira/REST/latest/) was the base document for this package. +The [latest Jira REST API documentation](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) was the base document for this package. ## Examples Further a few examples how the API can be used. -A few more examples are available in the [GoDoc examples section](https://godoc.org/github.com/andygrunwald/go-jira#pkg-examples). +A few more examples are available in the [GoDoc examples section](https://pkg.go.dev/github.com/andygrunwald/go-jira#section-directories). ### Get a single issue @@ -116,7 +116,7 @@ For convenience, capability for basic and cookie-based authentication is include #### Token (Jira on Atlassian Cloud) -Token-based authentication uses the basic authentication scheme, with a user-generated API token in place of a user's password. You can generate a token for your user [here](https://id.atlassian.com/manage-profile/security/api-tokens). Additional information about Atlassian Cloud API tokens can be found [here](https://confluence.atlassian.com/cloud/api-tokens-938839638.html). +Token-based authentication uses the basic authentication scheme, with a user-generated API token in place of a user's password. You can generate a token for your user [here](https://id.atlassian.com/manage-profile/security/api-tokens). Additional information about Atlassian Cloud API tokens can be found [here](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/). A more thorough, [runnable example](cloud/examples/basicauth/main.go) is provided in the examples directory. @@ -244,7 +244,7 @@ func main() { Jira API has limit on maxResults it can return. You may have a usecase where you need to get all issues for given JQL. This example shows reference implementation of GetAllIssues function which does pagination on Jira API to get all the issues for given JQL -please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/master/cloud/examples/pagination/main.go) +please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/main/cloud/examples/pagination/main.go) ### Call a not implemented API endpoint @@ -315,13 +315,13 @@ A few examples: * Implement a new feature or endpoint * Sharing the love of [go-jira](https://github.com/andygrunwald/go-jira) and help people to get use to it -If you are new to pull requests, checkout [Collaborating on projects using issues and pull requests / Creating a pull request](https://help.github.com/articles/creating-a-pull-request/). +If you are new to pull requests, checkout [Collaborating on projects using issues and pull requests / Creating a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request). ### Supported Go versions We follow the [Go Release Policy](https://go.dev/doc/devel/release#policy): -> Each major Go release is supported until there are two newer major releases. For example, Go 1.5 was supported until the Go 1.7 release, and Go 1.6 was supported until the Go 1.8 release. We fix critical problems, including [critical security problems](https://go.dev/security), in supported releases as needed by issuing minor revisions (for example, Go 1.6.1, Go 1.6.2, and so on). +> Each major Go release is supported until there are two newer major releases. For example, Go 1.5 was supported until the Go 1.7 release, and Go 1.6 was supported until the Go 1.8 release. We fix critical problems, including [critical security problems](https://go.dev/security/), in supported releases as needed by issuing minor revisions (for example, Go 1.6.1, Go 1.6.2, and so on). ### Supported Jira versions @@ -333,21 +333,21 @@ We follow the [Atlassian Support End of Life Policy](https://confluence.atlassia #### Jira Cloud -* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/) +* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/intro/) Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) is _currently_ not officially supported, because it is still in beta. ### Official Jira API documentation * [Jira Server (On-Premise solution)](https://developer.atlassian.com/server/jira/platform/rest-apis/) -* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/) +* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/intro/) * Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) ### Sandbox environment for testing Jira offers sandbox test environments at http://go.atlassian.com/cloud-dev. -You can read more about them at https://developer.atlassian.com/blog/2016/04/cloud-ecosystem-dev-env/. +You can read more about them at https://blog.developer.atlassian.com/cloud-ecosystem-dev-env/. ## Releasing From f430c66ea9ce5461a261e16659c81191f2cd7939 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 09:57:05 +0200 Subject: [PATCH 074/189] README: Removed the installation part for gopkg.in --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index 5a5429e..d001aaa 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,6 @@ It is go gettable go get github.com/andygrunwald/go-jira ``` -For stable versions you can use one of our tags with [gopkg.in](http://labix.org/gopkg.in). E.g. - -```go -package main - -import ( - jira "gopkg.in/andygrunwald/go-jira.v1" -) -... -``` - (optional) to run unit / example tests: ```bash From 7576942242478e476fe9dda4af45cdec35573848 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:01:01 +0200 Subject: [PATCH 075/189] Makefile: Switch to latest version of staticcheck --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e1f5008..48a693a 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ fmt: ## Runs go fmt (to check for go coding guidelines). .PHONY: staticcheck staticcheck: ## Runs static analysis to prevend bugs, foster code simplicity, performance and editor integration. - go install honnef.co/go/tools/cmd/staticcheck@2022.1 + go install honnef.co/go/tools/cmd/staticcheck@latest staticcheck ./... .PHONY: all From 262ab8d44271aa55db8e1382a76948a6c4c8b9d3 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:01:14 +0200 Subject: [PATCH 076/189] Makefile: Add commands for testing coverage --- .gitignore | 5 ++++- Makefile | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e200606..9d26f47 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,7 @@ _testmain.go *.prof *.iml .idea -.DS_Store \ No newline at end of file +.DS_Store + +# Code coverage +coverage.txt \ No newline at end of file diff --git a/Makefile b/Makefile index 48a693a..2ff82e3 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,15 @@ help: ## Outputs the help. .PHONY: test test: ## Runs all unit, integration and example tests. - go test -race -v ./... + go test -v -race ./... + +.PHONY: test-coverage +test-coverage: ## Runs all unit tests + gathers code coverage + go test -v -race -coverprofile coverage.txt ./... + +.PHONY: test-coverage-html +test-coverage-html: test-coverage ## Runs all unit tests + gathers code coverage + displays them in your default browser + go tool cover -html=coverage.txt .PHONY: vet vet: ## Runs go vet (to detect suspicious constructs). From 925add5b940d8d60aeb3151ea275340538c7445b Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:02:58 +0200 Subject: [PATCH 077/189] README: Add chapter about executing unit tests --- README.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d001aaa..108a5e9 100644 --- a/README.md +++ b/README.md @@ -48,17 +48,10 @@ of Go are officially supported. It is go gettable -```bash +```sh go get github.com/andygrunwald/go-jira ``` -(optional) to run unit / example tests: - -```bash -cd $GOPATH/src/github.com/andygrunwald/go-jira -go test -v ./... -``` - ## API Please have a look at the [GoDoc documentation](https://pkg.go.dev/github.com/andygrunwald/go-jira) for a detailed API description. @@ -282,7 +275,9 @@ func main() { * [andygrunwald/jitic](https://github.com/andygrunwald/jitic) - The Jira Ticket Checker -## Code structure +## Development + +### Code structure The code structure of this package was inspired by [google/go-github](https://github.com/google/go-github). @@ -290,6 +285,20 @@ There is one main part (the client). Based on this main client the other endpoints, like Issues or Authentication are extracted in services. E.g. `IssueService` or `AuthenticationService`. These services own a responsibility of the single endpoints / usecases of Jira. +### Unit testing + +To run the local unit tests, execute + +```sh +$ make test +``` + +To run the local unit tests and view the unit test code coverage in your local web browser, execute + +```sh +$ make test-coverage-html +``` + ## Contribution We ❤️ PR's From 16f9a8f7222aef08a5a75bdb6c349df160debf93 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:22:38 +0200 Subject: [PATCH 078/189] Updated changelog with latest changes --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d935f..a6f10d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -344,9 +344,14 @@ client.Project.GetAll(ctx, &GetQueryOptions{}) * UserAgent: Client HTTP calls are now identifable via a User Agent. This user agent can be configured (default: `go-jira/2.0.0`) * The underlying used HTTP client for API calls can be retrieved via `client.Client()` +### Bug Fixes + +* README: Fixed all (broken) links + ### Other * Replace all "GET", "POST", ... with http.MethodGet (and related) constants +* Development: Added `make` commands to collect (unit) test coverage ### Changes From 35154db04a8fe801cd11ce28be2c130421e11177 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:23:01 +0200 Subject: [PATCH 079/189] PersonalAccessToken Auth: Used fmt.Sprintf to concat a string --- cloud/auth_transport_personal_access_token.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cloud/auth_transport_personal_access_token.go b/cloud/auth_transport_personal_access_token.go index fb2f339..2661ac0 100644 --- a/cloud/auth_transport_personal_access_token.go +++ b/cloud/auth_transport_personal_access_token.go @@ -1,6 +1,9 @@ package cloud -import "net/http" +import ( + "fmt" + "net/http" +) // PATAuthTransport is an http.RoundTripper that authenticates all requests // using the Personal Access Token specified. @@ -18,7 +21,8 @@ type PATAuthTransport struct { // basic auth and return the RoundTripper for this transport type. func (t *PATAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { req2 := cloneRequest(req) // per RoundTripper contract - req2.Header.Set("Authorization", "Bearer "+t.Token) + + req2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.Token)) return t.transport().RoundTrip(req2) } From a2911195f912ba08e9608b9257c3d29d9e47247d Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:29:42 +0200 Subject: [PATCH 080/189] README: API-Version: Official support for Jira Cloud API in version 3 --- CHANGELOG.md | 1 + README.md | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6f10d9..4f84c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -343,6 +343,7 @@ client.Project.GetAll(ctx, &GetQueryOptions{}) * UserAgent: Client HTTP calls are now identifable via a User Agent. This user agent can be configured (default: `go-jira/2.0.0`) * The underlying used HTTP client for API calls can be retrieved via `client.Client()` +* API-Version: Official support for Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) ### Bug Fixes diff --git a/README.md b/README.md index 108a5e9..c30f564 100644 --- a/README.md +++ b/README.md @@ -331,9 +331,11 @@ We follow the [Atlassian Support End of Life Policy](https://confluence.atlassia #### Jira Cloud -* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/intro/) +We support Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/). +Even if this API version is _currently_ in beta (by Atlassian): -Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) is _currently_ not officially supported, because it is still in beta. +[Version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/) and version 3 of the API offer the same collection of operations. +However, version 3 provides support for the [Atlassian Document Format (ADF)](https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/) in a subset of the API. ### Official Jira API documentation From 107734e86be47b12525df0733c1687180803b934 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:59:27 +0200 Subject: [PATCH 081/189] Cloud/Status Category: Smaller godoc changes Related #577 --- cloud/statuscategory.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cloud/statuscategory.go b/cloud/statuscategory.go index a1e0439..7a33fa0 100644 --- a/cloud/statuscategory.go +++ b/cloud/statuscategory.go @@ -10,7 +10,6 @@ import ( // StatusCategoryService handles status categories for the Jira instance / API. // // Use it to obtain a list of all status categories and the details of a category. -// // Status categories provided a mechanism for categorizing statuses. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-group-workflow-status-categories @@ -34,7 +33,7 @@ const ( StatusCategoryUndefined = "undefined" ) -// GetList gets all status categories from Jira +// GetList returns a list of all status categories. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-get func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, *Response, error) { @@ -49,16 +48,17 @@ func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, if err != nil { return nil, resp, NewJiraError(resp, err) } + return statusCategories, resp, nil } // Get returns a status category. -// // Status categories provided a mechanism for categorizing statuses. // +// statusCategoryID represents the ID or key of the status category. +// // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-idorkey-get func (s *StatusCategoryService) Get(ctx context.Context, statusCategoryID string) (*StatusCategory, *Response, error) { - if statusCategoryID == "" { return nil, nil, errors.New("jira: not status category set") } From 46f6b7adda195a4be2a45948ea8b999e82ff5212 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:00:37 +0200 Subject: [PATCH 082/189] Cloud/Status category: Added two additional testing checks Related #577 --- cloud/statuscategory_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cloud/statuscategory_test.go b/cloud/statuscategory_test.go index 6d1b6dc..91942a3 100644 --- a/cloud/statuscategory_test.go +++ b/cloud/statuscategory_test.go @@ -17,6 +17,7 @@ func TestStatusCategoryService_GetList(t *testing.T) { if err != nil { t.Error(err.Error()) } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) @@ -27,6 +28,9 @@ func TestStatusCategoryService_GetList(t *testing.T) { if statusCategory == nil { t.Error("Expected statusCategory list. StatusCategory list is nil") } + if l := len(statusCategory); l != 4 { + t.Errorf("Expected 4 statusCategory list items. Got %d", l) + } if err != nil { t.Errorf("Error given: %s", err) } @@ -41,6 +45,7 @@ func TestStatusCategoryService_Get(t *testing.T) { if err != nil { t.Error(err.Error()) } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) @@ -48,10 +53,13 @@ func TestStatusCategoryService_Get(t *testing.T) { }) statusCategory, _, err := testClient.StatusCategory.Get(context.Background(), "1") - if err != nil { t.Errorf("Error given: %s", err) } else if statusCategory == nil { t.Error("Expected status category. StatusCategory is nil") + + // Checking testdata + } else if statusCategory.ColorName != "medium-gray" { + t.Errorf("Expected statusCategory.ColorName to be 'medium-gray'. Got '%s'", statusCategory.ColorName) } } From 58243b0aaf0edb4d5f2d1c6d9cbd64707a8819e5 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:04:11 +0200 Subject: [PATCH 083/189] Cloud/Status category: Add comments for usage example Related #577 --- .../main.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) rename cloud/examples/{getthestatuscategories => statuscategories}/main.go (65%) diff --git a/cloud/examples/getthestatuscategories/main.go b/cloud/examples/statuscategories/main.go similarity index 65% rename from cloud/examples/getthestatuscategories/main.go rename to cloud/examples/statuscategories/main.go index 02c343d..9a9f7f7 100644 --- a/cloud/examples/getthestatuscategories/main.go +++ b/cloud/examples/statuscategories/main.go @@ -2,14 +2,19 @@ package main import ( "context" - jira "github.com/andygrunwald/go-jira/v2/cloud" "log" + + jira "github.com/andygrunwald/go-jira/v2/cloud" ) func main() { + jiraClient, err := jira.NewClient("https://mattermost.atlassian.net/", nil) + if err != nil { + panic(err) + } - jiraClient, _ := jira.NewClient("https://mattermost.atlassian.net/", nil) - + // Showcase of StatusCategory.GetList: + // Getting all status categories categories, resp, err := jiraClient.StatusCategory.GetList(context.TODO()) if err != nil { log.Println(resp.StatusCode) @@ -20,6 +25,8 @@ func main() { log.Println(statusCategory) } + // Showcase of StatusCategory.Get + // Getting a single status category category, resp, err := jiraClient.StatusCategory.Get(context.TODO(), "1") if err != nil { log.Println(resp.StatusCode) From 2782ff34287a72e165efc09fb76f23cd5f4832f9 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:06:07 +0200 Subject: [PATCH 084/189] On premise/Status category: Add comments for usage example Related #577 --- .../main.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) rename onpremise/examples/{getthestatuscategories => statuscategories}/main.go (66%) diff --git a/onpremise/examples/getthestatuscategories/main.go b/onpremise/examples/statuscategories/main.go similarity index 66% rename from onpremise/examples/getthestatuscategories/main.go rename to onpremise/examples/statuscategories/main.go index a0a6565..364b7c4 100644 --- a/onpremise/examples/getthestatuscategories/main.go +++ b/onpremise/examples/statuscategories/main.go @@ -2,14 +2,19 @@ package main import ( "context" - jira "github.com/andygrunwald/go-jira/v2/onpremise" "log" + + jira "github.com/andygrunwald/go-jira/v2/onpremise" ) func main() { + jiraClient, err := jira.NewClient("https://issues.apache.org/jira/", nil) + if err != nil { + panic(err) + } - jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", nil) - + // Showcase of StatusCategory.GetList: + // Getting all status categories categories, resp, err := jiraClient.StatusCategory.GetList(context.TODO()) if err != nil { log.Println(resp.StatusCode) @@ -20,6 +25,8 @@ func main() { log.Println(statusCategory) } + // Showcase of StatusCategory.Get + // Getting a single status category category, resp, err := jiraClient.StatusCategory.Get(context.TODO(), "1") if err != nil { log.Println(resp.StatusCode) From 238ae3925368651ff85f219061d46198e749cd8e Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:14:56 +0200 Subject: [PATCH 085/189] Cloud/Status category: fixed error message if no status category id is given --- cloud/statuscategory.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/statuscategory.go b/cloud/statuscategory.go index 7a33fa0..5918f7c 100644 --- a/cloud/statuscategory.go +++ b/cloud/statuscategory.go @@ -60,7 +60,7 @@ func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-idorkey-get func (s *StatusCategoryService) Get(ctx context.Context, statusCategoryID string) (*StatusCategory, *Response, error) { if statusCategoryID == "" { - return nil, nil, errors.New("jira: not status category set") + return nil, nil, errors.New("no status category id set") } apiEndpoint := fmt.Sprintf("/rest/api/3/statuscategory/%v", statusCategoryID) From 0f083295b6d715d2392253396ef97ad27feabe6a Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:15:27 +0200 Subject: [PATCH 086/189] Cloud/Status category: Added empty line to improve readability --- cloud/statuscategory_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud/statuscategory_test.go b/cloud/statuscategory_test.go index 91942a3..af7aab8 100644 --- a/cloud/statuscategory_test.go +++ b/cloud/statuscategory_test.go @@ -55,6 +55,7 @@ func TestStatusCategoryService_Get(t *testing.T) { statusCategory, _, err := testClient.StatusCategory.Get(context.Background(), "1") if err != nil { t.Errorf("Error given: %s", err) + } else if statusCategory == nil { t.Error("Expected status category. StatusCategory is nil") From b868203ec27eb502dff938ef2abbfb8f841e8709 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:16:01 +0200 Subject: [PATCH 087/189] On premise/Status category: Fixed godoc and links to Jira documentation --- onpremise/statuscategory.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/onpremise/statuscategory.go b/onpremise/statuscategory.go index bc8c84f..7553459 100644 --- a/onpremise/statuscategory.go +++ b/onpremise/statuscategory.go @@ -10,7 +10,6 @@ import ( // StatusCategoryService handles status categories for the Jira instance / API. // // Use it to obtain a list of all status categories and the details of a category. -// // Status categories provided a mechanism for categorizing statuses. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-workflow-status-categories/#api-group-workflow-status-categories @@ -34,9 +33,9 @@ const ( StatusCategoryUndefined = "undefined" ) -// GetList gets all status categories from Jira +// GetList returns a list of all status categories. // -// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-workflow-status-categories/#api-rest-api-2-statuscategory-get +// Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/statuscategory-getStatusCategories func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, *Response, error) { apiEndpoint := "/rest/api/2/statuscategory" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -49,18 +48,18 @@ func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, if err != nil { return nil, resp, NewJiraError(resp, err) } + return statusCategories, resp, nil } -// Get returns a status category. +// Get returns a full representation of the StatusCategory having the given id or key. // -// Status categories provided a mechanism for categorizing statuses. +// statusCategoryID represents the ID or key of the status category. // -// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-workflow-status-categories/#api-rest-api-2-statuscategory-idorkey-get +// Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/statuscategory-getStatusCategory func (s *StatusCategoryService) Get(ctx context.Context, statusCategoryID string) (*StatusCategory, *Response, error) { - if statusCategoryID == "" { - return nil, nil, errors.New("jira: not status category set") + return nil, nil, errors.New("no status category id set") } apiEndpoint := fmt.Sprintf("/rest/api/2/statuscategory/%v", statusCategoryID) From 2f711220a3586c36c6bec9cf6f9cf40064865f1f Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:16:35 +0200 Subject: [PATCH 088/189] On premise/Status category: Added an additional test check --- onpremise/statuscategory_test.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/onpremise/statuscategory_test.go b/onpremise/statuscategory_test.go index cc2b9dc..be27db3 100644 --- a/onpremise/statuscategory_test.go +++ b/onpremise/statuscategory_test.go @@ -11,15 +11,16 @@ import ( func TestStatusCategoryService_GetList(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/statuscategory" + testAPIEndpoint := "/rest/api/2/statuscategory" raw, err := os.ReadFile("../testing/mock-data/all_statuscategories.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -27,6 +28,9 @@ func TestStatusCategoryService_GetList(t *testing.T) { if statusCategory == nil { t.Error("Expected statusCategory list. StatusCategory list is nil") } + if l := len(statusCategory); l != 4 { + t.Errorf("Expected 4 statusCategory list items. Got %d", l) + } if err != nil { t.Errorf("Error given: %s", err) } @@ -41,6 +45,7 @@ func TestStatusCategoryService_Get(t *testing.T) { if err != nil { t.Error(err.Error()) } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) @@ -48,10 +53,14 @@ func TestStatusCategoryService_Get(t *testing.T) { }) statusCategory, _, err := testClient.StatusCategory.Get(context.Background(), "1") - if err != nil { t.Errorf("Error given: %s", err) + } else if statusCategory == nil { t.Error("Expected status category. StatusCategory is nil") + + // Checking testdata + } else if statusCategory.ColorName != "medium-gray" { + t.Errorf("Expected statusCategory.ColorName to be 'medium-gray'. Got '%s'", statusCategory.ColorName) } } From c2943e25e82985dd56cf1909fd7d41d5dae33df6 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:19:58 +0200 Subject: [PATCH 089/189] CHANGELOG: Workflow status categories: Revisited and fully implemented for Cloud and On Premise (incl. examples) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f84c07..335aaf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -349,6 +349,10 @@ client.Project.GetAll(ctx, &GetQueryOptions{}) * README: Fixed all (broken) links +### API-Endpoints + +* Workflow status categories: Revisited and fully implemented for Cloud and On Premise (incl. examples) + ### Other * Replace all "GET", "POST", ... with http.MethodGet (and related) constants From 4f9769924c5a4170cfbc77ad6b2bcc4120fcacc0 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:23:14 +0200 Subject: [PATCH 090/189] CHANGELOG: Removed git conflict marker --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a631f14..335aaf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -348,13 +348,10 @@ client.Project.GetAll(ctx, &GetQueryOptions{}) ### Bug Fixes * README: Fixed all (broken) links -<<<<<<< HEAD -======= ### API-Endpoints * Workflow status categories: Revisited and fully implemented for Cloud and On Premise (incl. examples) ->>>>>>> ctreminiom-feature/552 ### Other From 9ba3fcd83d5cd98d72e1c47d7ecb39f591c1d65d Mon Sep 17 00:00:00 2001 From: Maciej Pijanowski Date: Mon, 26 Sep 2022 13:52:29 +0200 Subject: [PATCH 091/189] README.md: fix example URLs Signed-off-by: Maciej Pijanowski --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4c4dd77..925860a 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ For convenience, capability for basic and cookie-based authentication is include Token-based authentication uses the basic authentication scheme, with a user-generated API token in place of a user's password. You can generate a token for your user [here](https://id.atlassian.com/manage-profile/security/api-tokens). Additional information about Atlassian Cloud API tokens can be found [here](https://confluence.atlassian.com/cloud/api-tokens-938839638.html). -A more thorough, [runnable example](examples/basicauth/main.go) is provided in the examples directory. +A more thorough, [runnable example](cloud/examples/basicauth/main.go) is provided in the examples directory. ```go func main() { @@ -244,10 +244,7 @@ func main() { Jira API has limit on maxResults it can return. You may have a usecase where you need to get all issues for given JQL. This example shows reference implementation of GetAllIssues function which does pagination on Jira API to get all the issues for given JQL -please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/master/examples/pagination/main.go) - - - +please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/master/cloud/examples/pagination/main.go) ### Call a not implemented API endpoint From a61e20664d889bc841693939c291d6c28e62acab Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 09:55:19 +0200 Subject: [PATCH 092/189] Fix all URLs in the README This is a follow up from #576 --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 925860a..5a5429e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # go-jira -[![GoDoc](https://godoc.org/github.com/andygrunwald/go-jira?status.svg)](https://godoc.org/github.com/andygrunwald/go-jira) +[![GoDoc](https://pkg.go.dev/badge/github.com/andygrunwald/go-jira?utm_source=godoc)](https://pkg.go.dev/github.com/andygrunwald/go-jira) [![Build Status](https://github.com/andygrunwald/go-jira/actions/workflows/testing.yml/badge.svg)](https://github.com/andygrunwald/go-jira/actions/workflows/testing.yml) -[![Go Report Card](https://goreportcard.com/badge/github.com/andygrunwald/go-jira)](https://goreportcard.com/report/github.com/andygrunwald/go-jira) +[![Go Report Card](https://img.shields.io/badge/go%20report-A+-brightgreen.svg?style=flat)](https://goreportcard.com/report/github.com/andygrunwald/go-jira) -[Go](https://golang.org/) client library for [Atlassian Jira](https://www.atlassian.com/software/jira). +[Go](https://go.dev/) client library for [Atlassian Jira](https://www.atlassian.com/software/jira). ![Go client library for Atlassian Jira](./img/logo_small.png "Go client library for Atlassian Jira.") @@ -34,7 +34,7 @@ Latest stable release: [v1.16.0](https://github.com/andygrunwald/go-jira/release * Create and retrieve issue transitions (status updates) * Call every API endpoint of the Jira, even if it is not directly implemented in this library -This package is not Jira API complete (yet), but you can call every API endpoint you want. See [Call a not implemented API endpoint](#call-a-not-implemented-api-endpoint) how to do this. For all possible API endpoints of Jira have a look at [latest Jira REST API documentation](https://docs.atlassian.com/jira/REST/latest/). +This package is not Jira API complete (yet), but you can call every API endpoint you want. See [Call a not implemented API endpoint](#call-a-not-implemented-api-endpoint) how to do this. For all possible API endpoints of Jira have a look at [latest Jira REST API documentation](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/). ## Requirements @@ -72,14 +72,14 @@ go test -v ./... ## API -Please have a look at the [GoDoc documentation](https://godoc.org/github.com/andygrunwald/go-jira) for a detailed API description. +Please have a look at the [GoDoc documentation](https://pkg.go.dev/github.com/andygrunwald/go-jira) for a detailed API description. -The [latest Jira REST API documentation](https://docs.atlassian.com/jira/REST/latest/) was the base document for this package. +The [latest Jira REST API documentation](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) was the base document for this package. ## Examples Further a few examples how the API can be used. -A few more examples are available in the [GoDoc examples section](https://godoc.org/github.com/andygrunwald/go-jira#pkg-examples). +A few more examples are available in the [GoDoc examples section](https://pkg.go.dev/github.com/andygrunwald/go-jira#section-directories). ### Get a single issue @@ -116,7 +116,7 @@ For convenience, capability for basic and cookie-based authentication is include #### Token (Jira on Atlassian Cloud) -Token-based authentication uses the basic authentication scheme, with a user-generated API token in place of a user's password. You can generate a token for your user [here](https://id.atlassian.com/manage-profile/security/api-tokens). Additional information about Atlassian Cloud API tokens can be found [here](https://confluence.atlassian.com/cloud/api-tokens-938839638.html). +Token-based authentication uses the basic authentication scheme, with a user-generated API token in place of a user's password. You can generate a token for your user [here](https://id.atlassian.com/manage-profile/security/api-tokens). Additional information about Atlassian Cloud API tokens can be found [here](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/). A more thorough, [runnable example](cloud/examples/basicauth/main.go) is provided in the examples directory. @@ -244,7 +244,7 @@ func main() { Jira API has limit on maxResults it can return. You may have a usecase where you need to get all issues for given JQL. This example shows reference implementation of GetAllIssues function which does pagination on Jira API to get all the issues for given JQL -please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/master/cloud/examples/pagination/main.go) +please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/main/cloud/examples/pagination/main.go) ### Call a not implemented API endpoint @@ -315,13 +315,13 @@ A few examples: * Implement a new feature or endpoint * Sharing the love of [go-jira](https://github.com/andygrunwald/go-jira) and help people to get use to it -If you are new to pull requests, checkout [Collaborating on projects using issues and pull requests / Creating a pull request](https://help.github.com/articles/creating-a-pull-request/). +If you are new to pull requests, checkout [Collaborating on projects using issues and pull requests / Creating a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request). ### Supported Go versions We follow the [Go Release Policy](https://go.dev/doc/devel/release#policy): -> Each major Go release is supported until there are two newer major releases. For example, Go 1.5 was supported until the Go 1.7 release, and Go 1.6 was supported until the Go 1.8 release. We fix critical problems, including [critical security problems](https://go.dev/security), in supported releases as needed by issuing minor revisions (for example, Go 1.6.1, Go 1.6.2, and so on). +> Each major Go release is supported until there are two newer major releases. For example, Go 1.5 was supported until the Go 1.7 release, and Go 1.6 was supported until the Go 1.8 release. We fix critical problems, including [critical security problems](https://go.dev/security/), in supported releases as needed by issuing minor revisions (for example, Go 1.6.1, Go 1.6.2, and so on). ### Supported Jira versions @@ -333,21 +333,21 @@ We follow the [Atlassian Support End of Life Policy](https://confluence.atlassia #### Jira Cloud -* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/) +* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/intro/) Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) is _currently_ not officially supported, because it is still in beta. ### Official Jira API documentation * [Jira Server (On-Premise solution)](https://developer.atlassian.com/server/jira/platform/rest-apis/) -* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/) +* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/intro/) * Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) ### Sandbox environment for testing Jira offers sandbox test environments at http://go.atlassian.com/cloud-dev. -You can read more about them at https://developer.atlassian.com/blog/2016/04/cloud-ecosystem-dev-env/. +You can read more about them at https://blog.developer.atlassian.com/cloud-ecosystem-dev-env/. ## Releasing From 7cca85edc0280a1f03b5e7431bde0c0b05520a5b Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 09:57:05 +0200 Subject: [PATCH 093/189] README: Removed the installation part for gopkg.in --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index 5a5429e..d001aaa 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,6 @@ It is go gettable go get github.com/andygrunwald/go-jira ``` -For stable versions you can use one of our tags with [gopkg.in](http://labix.org/gopkg.in). E.g. - -```go -package main - -import ( - jira "gopkg.in/andygrunwald/go-jira.v1" -) -... -``` - (optional) to run unit / example tests: ```bash From 2b74414b7c0f9a063d6c4e6e1c29ea2e5bf1cad7 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:01:01 +0200 Subject: [PATCH 094/189] Makefile: Switch to latest version of staticcheck --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e1f5008..48a693a 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ fmt: ## Runs go fmt (to check for go coding guidelines). .PHONY: staticcheck staticcheck: ## Runs static analysis to prevend bugs, foster code simplicity, performance and editor integration. - go install honnef.co/go/tools/cmd/staticcheck@2022.1 + go install honnef.co/go/tools/cmd/staticcheck@latest staticcheck ./... .PHONY: all From 0f45b6886dff88b59affca75cecc4b80f5beded3 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:01:14 +0200 Subject: [PATCH 095/189] Makefile: Add commands for testing coverage --- .gitignore | 5 ++++- Makefile | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e200606..9d26f47 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,7 @@ _testmain.go *.prof *.iml .idea -.DS_Store \ No newline at end of file +.DS_Store + +# Code coverage +coverage.txt \ No newline at end of file diff --git a/Makefile b/Makefile index 48a693a..2ff82e3 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,15 @@ help: ## Outputs the help. .PHONY: test test: ## Runs all unit, integration and example tests. - go test -race -v ./... + go test -v -race ./... + +.PHONY: test-coverage +test-coverage: ## Runs all unit tests + gathers code coverage + go test -v -race -coverprofile coverage.txt ./... + +.PHONY: test-coverage-html +test-coverage-html: test-coverage ## Runs all unit tests + gathers code coverage + displays them in your default browser + go tool cover -html=coverage.txt .PHONY: vet vet: ## Runs go vet (to detect suspicious constructs). From 22e4eef696082e4c0d2668416025c175d4da150a Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:02:58 +0200 Subject: [PATCH 096/189] README: Add chapter about executing unit tests --- README.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d001aaa..108a5e9 100644 --- a/README.md +++ b/README.md @@ -48,17 +48,10 @@ of Go are officially supported. It is go gettable -```bash +```sh go get github.com/andygrunwald/go-jira ``` -(optional) to run unit / example tests: - -```bash -cd $GOPATH/src/github.com/andygrunwald/go-jira -go test -v ./... -``` - ## API Please have a look at the [GoDoc documentation](https://pkg.go.dev/github.com/andygrunwald/go-jira) for a detailed API description. @@ -282,7 +275,9 @@ func main() { * [andygrunwald/jitic](https://github.com/andygrunwald/jitic) - The Jira Ticket Checker -## Code structure +## Development + +### Code structure The code structure of this package was inspired by [google/go-github](https://github.com/google/go-github). @@ -290,6 +285,20 @@ There is one main part (the client). Based on this main client the other endpoints, like Issues or Authentication are extracted in services. E.g. `IssueService` or `AuthenticationService`. These services own a responsibility of the single endpoints / usecases of Jira. +### Unit testing + +To run the local unit tests, execute + +```sh +$ make test +``` + +To run the local unit tests and view the unit test code coverage in your local web browser, execute + +```sh +$ make test-coverage-html +``` + ## Contribution We ❤️ PR's From 6e8bdbfc4c33b740c7c584c3b9c5ca122c65130e Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:22:38 +0200 Subject: [PATCH 097/189] Updated changelog with latest changes --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d935f..a6f10d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -344,9 +344,14 @@ client.Project.GetAll(ctx, &GetQueryOptions{}) * UserAgent: Client HTTP calls are now identifable via a User Agent. This user agent can be configured (default: `go-jira/2.0.0`) * The underlying used HTTP client for API calls can be retrieved via `client.Client()` +### Bug Fixes + +* README: Fixed all (broken) links + ### Other * Replace all "GET", "POST", ... with http.MethodGet (and related) constants +* Development: Added `make` commands to collect (unit) test coverage ### Changes From 325069c115071df04bc8f20a955878817f43256f Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:23:01 +0200 Subject: [PATCH 098/189] PersonalAccessToken Auth: Used fmt.Sprintf to concat a string --- cloud/auth_transport_personal_access_token.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cloud/auth_transport_personal_access_token.go b/cloud/auth_transport_personal_access_token.go index fb2f339..2661ac0 100644 --- a/cloud/auth_transport_personal_access_token.go +++ b/cloud/auth_transport_personal_access_token.go @@ -1,6 +1,9 @@ package cloud -import "net/http" +import ( + "fmt" + "net/http" +) // PATAuthTransport is an http.RoundTripper that authenticates all requests // using the Personal Access Token specified. @@ -18,7 +21,8 @@ type PATAuthTransport struct { // basic auth and return the RoundTripper for this transport type. func (t *PATAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { req2 := cloneRequest(req) // per RoundTripper contract - req2.Header.Set("Authorization", "Bearer "+t.Token) + + req2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.Token)) return t.transport().RoundTrip(req2) } From 70e965ebd2965f9e9a59ea46b6466b80d7166c0a Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:29:42 +0200 Subject: [PATCH 099/189] README: API-Version: Official support for Jira Cloud API in version 3 --- CHANGELOG.md | 1 + README.md | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6f10d9..4f84c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -343,6 +343,7 @@ client.Project.GetAll(ctx, &GetQueryOptions{}) * UserAgent: Client HTTP calls are now identifable via a User Agent. This user agent can be configured (default: `go-jira/2.0.0`) * The underlying used HTTP client for API calls can be retrieved via `client.Client()` +* API-Version: Official support for Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) ### Bug Fixes diff --git a/README.md b/README.md index 108a5e9..c30f564 100644 --- a/README.md +++ b/README.md @@ -331,9 +331,11 @@ We follow the [Atlassian Support End of Life Policy](https://confluence.atlassia #### Jira Cloud -* Jira Cloud API in [version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/intro/) +We support Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/). +Even if this API version is _currently_ in beta (by Atlassian): -Jira Cloud API in [version 3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/) is _currently_ not officially supported, because it is still in beta. +[Version 2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/) and version 3 of the API offer the same collection of operations. +However, version 3 provides support for the [Atlassian Document Format (ADF)](https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/) in a subset of the API. ### Official Jira API documentation From 35e402f91fcf818384da34dce683a0f763d056ea Mon Sep 17 00:00:00 2001 From: Carlos Treminio Date: Sun, 9 Oct 2022 21:00:15 -0600 Subject: [PATCH 100/189] :recycle: IssueCategory Service reviewed 1. The Get() method has been created on the cloud and onPremise versions. 2. The JSON schema for the Cloud and OnPremise products are the same. 3. The Test Cases have been created in both modules. 4. Created an example of the new implementation 5. resolves #552 6. resolved #530 --- cloud/examples/getthestatuscategories/main.go | 30 +++++++++++++ cloud/statuscategory.go | 44 ++++++++++++++++--- cloud/statuscategory_test.go | 30 +++++++++++-- .../examples/getthestatuscategories/main.go | 30 +++++++++++++ onpremise/statuscategory.go | 44 ++++++++++++++++--- onpremise/statuscategory_test.go | 24 ++++++++++ testing/mock-data/status_category.json | 7 +++ 7 files changed, 194 insertions(+), 15 deletions(-) create mode 100644 cloud/examples/getthestatuscategories/main.go create mode 100644 onpremise/examples/getthestatuscategories/main.go create mode 100644 testing/mock-data/status_category.json diff --git a/cloud/examples/getthestatuscategories/main.go b/cloud/examples/getthestatuscategories/main.go new file mode 100644 index 0000000..02c343d --- /dev/null +++ b/cloud/examples/getthestatuscategories/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + jira "github.com/andygrunwald/go-jira/v2/cloud" + "log" +) + +func main() { + + jiraClient, _ := jira.NewClient("https://mattermost.atlassian.net/", nil) + + categories, resp, err := jiraClient.StatusCategory.GetList(context.TODO()) + if err != nil { + log.Println(resp.StatusCode) + panic(err) + } + + for _, statusCategory := range categories { + log.Println(statusCategory) + } + + category, resp, err := jiraClient.StatusCategory.Get(context.TODO(), "1") + if err != nil { + log.Println(resp.StatusCode) + panic(err) + } + + log.Println(category) +} diff --git a/cloud/statuscategory.go b/cloud/statuscategory.go index 74dc4c7..a1e0439 100644 --- a/cloud/statuscategory.go +++ b/cloud/statuscategory.go @@ -2,12 +2,18 @@ package cloud import ( "context" + "errors" + "fmt" "net/http" ) // StatusCategoryService handles status categories for the Jira instance / API. // -// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Statuscategory +// Use it to obtain a list of all status categories and the details of a category. +// +// Status categories provided a mechanism for categorizing statuses. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-group-workflow-status-categories type StatusCategoryService service // StatusCategory represents the category a status belongs to. @@ -30,18 +36,44 @@ const ( // GetList gets all status categories from Jira // -// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-get func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, *Response, error) { - apiEndpoint := "rest/api/2/statuscategory" + apiEndpoint := "/rest/api/3/statuscategory" + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + var statusCategories []StatusCategory + resp, err := s.client.Do(req, &statusCategories) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return statusCategories, resp, nil +} + +// Get returns a status category. +// +// Status categories provided a mechanism for categorizing statuses. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-idorkey-get +func (s *StatusCategoryService) Get(ctx context.Context, statusCategoryID string) (*StatusCategory, *Response, error) { + + if statusCategoryID == "" { + return nil, nil, errors.New("jira: not status category set") + } + + apiEndpoint := fmt.Sprintf("/rest/api/3/statuscategory/%v", statusCategoryID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } - statusCategoryList := []StatusCategory{} - resp, err := s.client.Do(req, &statusCategoryList) + statusCategory := new(StatusCategory) + resp, err := s.client.Do(req, statusCategory) if err != nil { return nil, resp, NewJiraError(resp, err) } - return statusCategoryList, resp, nil + + return statusCategory, resp, nil } diff --git a/cloud/statuscategory_test.go b/cloud/statuscategory_test.go index f05705a..6d1b6dc 100644 --- a/cloud/statuscategory_test.go +++ b/cloud/statuscategory_test.go @@ -11,15 +11,15 @@ import ( func TestStatusCategoryService_GetList(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/statuscategory" + testAPIEndpoint := "/rest/api/3/statuscategory" raw, err := os.ReadFile("../testing/mock-data/all_statuscategories.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -31,3 +31,27 @@ func TestStatusCategoryService_GetList(t *testing.T) { t.Errorf("Error given: %s", err) } } + +func TestStatusCategoryService_Get(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/3/statuscategory/1" + + raw, err := os.ReadFile("../testing/mock-data/status_category.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + statusCategory, _, err := testClient.StatusCategory.Get(context.Background(), "1") + + if err != nil { + t.Errorf("Error given: %s", err) + } else if statusCategory == nil { + t.Error("Expected status category. StatusCategory is nil") + } +} diff --git a/onpremise/examples/getthestatuscategories/main.go b/onpremise/examples/getthestatuscategories/main.go new file mode 100644 index 0000000..a0a6565 --- /dev/null +++ b/onpremise/examples/getthestatuscategories/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + jira "github.com/andygrunwald/go-jira/v2/onpremise" + "log" +) + +func main() { + + jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", nil) + + categories, resp, err := jiraClient.StatusCategory.GetList(context.TODO()) + if err != nil { + log.Println(resp.StatusCode) + panic(err) + } + + for _, statusCategory := range categories { + log.Println(statusCategory) + } + + category, resp, err := jiraClient.StatusCategory.Get(context.TODO(), "1") + if err != nil { + log.Println(resp.StatusCode) + panic(err) + } + + log.Println(category) +} diff --git a/onpremise/statuscategory.go b/onpremise/statuscategory.go index 3178c63..bc8c84f 100644 --- a/onpremise/statuscategory.go +++ b/onpremise/statuscategory.go @@ -2,12 +2,18 @@ package onpremise import ( "context" + "errors" + "fmt" "net/http" ) // StatusCategoryService handles status categories for the Jira instance / API. // -// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Statuscategory +// Use it to obtain a list of all status categories and the details of a category. +// +// Status categories provided a mechanism for categorizing statuses. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-workflow-status-categories/#api-group-workflow-status-categories type StatusCategoryService service // StatusCategory represents the category a status belongs to. @@ -30,18 +36,44 @@ const ( // GetList gets all status categories from Jira // -// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-workflow-status-categories/#api-rest-api-2-statuscategory-get func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, *Response, error) { - apiEndpoint := "rest/api/2/statuscategory" + apiEndpoint := "/rest/api/2/statuscategory" + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + var statusCategories []StatusCategory + resp, err := s.client.Do(req, &statusCategories) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + return statusCategories, resp, nil +} + +// Get returns a status category. +// +// Status categories provided a mechanism for categorizing statuses. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-workflow-status-categories/#api-rest-api-2-statuscategory-idorkey-get +func (s *StatusCategoryService) Get(ctx context.Context, statusCategoryID string) (*StatusCategory, *Response, error) { + + if statusCategoryID == "" { + return nil, nil, errors.New("jira: not status category set") + } + + apiEndpoint := fmt.Sprintf("/rest/api/2/statuscategory/%v", statusCategoryID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } - statusCategoryList := []StatusCategory{} - resp, err := s.client.Do(req, &statusCategoryList) + statusCategory := new(StatusCategory) + resp, err := s.client.Do(req, statusCategory) if err != nil { return nil, resp, NewJiraError(resp, err) } - return statusCategoryList, resp, nil + + return statusCategory, resp, nil } diff --git a/onpremise/statuscategory_test.go b/onpremise/statuscategory_test.go index 93af9d7..cc2b9dc 100644 --- a/onpremise/statuscategory_test.go +++ b/onpremise/statuscategory_test.go @@ -31,3 +31,27 @@ func TestStatusCategoryService_GetList(t *testing.T) { t.Errorf("Error given: %s", err) } } + +func TestStatusCategoryService_Get(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/2/statuscategory/1" + + raw, err := os.ReadFile("../testing/mock-data/status_category.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + statusCategory, _, err := testClient.StatusCategory.Get(context.Background(), "1") + + if err != nil { + t.Errorf("Error given: %s", err) + } else if statusCategory == nil { + t.Error("Expected status category. StatusCategory is nil") + } +} diff --git a/testing/mock-data/status_category.json b/testing/mock-data/status_category.json new file mode 100644 index 0000000..6302477 --- /dev/null +++ b/testing/mock-data/status_category.json @@ -0,0 +1,7 @@ +{ + "self": "https://issues.apache.org/jira/rest/api/2/resolution/1", + "id": 1, + "key": "undefined", + "colorName": "medium-gray", + "name": "No Category" +} \ No newline at end of file From a2931c3a2bc9a5ea00de34740e9bc8ec54e9f39e Mon Sep 17 00:00:00 2001 From: Maciej Pijanowski Date: Mon, 26 Sep 2022 13:52:29 +0200 Subject: [PATCH 101/189] README.md: fix example URLs Signed-off-by: Maciej Pijanowski --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c30f564..ce1c78c 100644 --- a/README.md +++ b/README.md @@ -223,10 +223,11 @@ func main() { } ``` ### Get all the issues for JQL with Pagination + Jira API has limit on maxResults it can return. You may have a usecase where you need to get all issues for given JQL. -This example shows reference implementation of GetAllIssues function which does pagination on Jira API to get all the issues for given JQL +This example shows reference implementation of GetAllIssues function which does pagination on Jira API to get all the issues for given JQL. -please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/main/cloud/examples/pagination/main.go) +Please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/main/cloud/examples/pagination/main.go) ### Call a not implemented API endpoint From af93a3b7ccb71ba5b9f3e18cda9ad18c8d4d9a1a Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:22:38 +0200 Subject: [PATCH 102/189] Updated changelog with latest changes --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f84c07..c87682b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -349,6 +349,10 @@ client.Project.GetAll(ctx, &GetQueryOptions{}) * README: Fixed all (broken) links +### Bug Fixes + +* README: Fixed all (broken) links + ### Other * Replace all "GET", "POST", ... with http.MethodGet (and related) constants From d75702fc6d0cfbd5a3509d8e35763e1d643c3b59 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 10:59:27 +0200 Subject: [PATCH 103/189] Cloud/Status Category: Smaller godoc changes Related #577 --- cloud/statuscategory.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cloud/statuscategory.go b/cloud/statuscategory.go index a1e0439..7a33fa0 100644 --- a/cloud/statuscategory.go +++ b/cloud/statuscategory.go @@ -10,7 +10,6 @@ import ( // StatusCategoryService handles status categories for the Jira instance / API. // // Use it to obtain a list of all status categories and the details of a category. -// // Status categories provided a mechanism for categorizing statuses. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-group-workflow-status-categories @@ -34,7 +33,7 @@ const ( StatusCategoryUndefined = "undefined" ) -// GetList gets all status categories from Jira +// GetList returns a list of all status categories. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-get func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, *Response, error) { @@ -49,16 +48,17 @@ func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, if err != nil { return nil, resp, NewJiraError(resp, err) } + return statusCategories, resp, nil } // Get returns a status category. -// // Status categories provided a mechanism for categorizing statuses. // +// statusCategoryID represents the ID or key of the status category. +// // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-idorkey-get func (s *StatusCategoryService) Get(ctx context.Context, statusCategoryID string) (*StatusCategory, *Response, error) { - if statusCategoryID == "" { return nil, nil, errors.New("jira: not status category set") } From 6a1fe208c8bed0ae8993b82284e9a5fc0116bd2d Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:00:37 +0200 Subject: [PATCH 104/189] Cloud/Status category: Added two additional testing checks Related #577 --- cloud/statuscategory_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cloud/statuscategory_test.go b/cloud/statuscategory_test.go index 6d1b6dc..91942a3 100644 --- a/cloud/statuscategory_test.go +++ b/cloud/statuscategory_test.go @@ -17,6 +17,7 @@ func TestStatusCategoryService_GetList(t *testing.T) { if err != nil { t.Error(err.Error()) } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) @@ -27,6 +28,9 @@ func TestStatusCategoryService_GetList(t *testing.T) { if statusCategory == nil { t.Error("Expected statusCategory list. StatusCategory list is nil") } + if l := len(statusCategory); l != 4 { + t.Errorf("Expected 4 statusCategory list items. Got %d", l) + } if err != nil { t.Errorf("Error given: %s", err) } @@ -41,6 +45,7 @@ func TestStatusCategoryService_Get(t *testing.T) { if err != nil { t.Error(err.Error()) } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) @@ -48,10 +53,13 @@ func TestStatusCategoryService_Get(t *testing.T) { }) statusCategory, _, err := testClient.StatusCategory.Get(context.Background(), "1") - if err != nil { t.Errorf("Error given: %s", err) } else if statusCategory == nil { t.Error("Expected status category. StatusCategory is nil") + + // Checking testdata + } else if statusCategory.ColorName != "medium-gray" { + t.Errorf("Expected statusCategory.ColorName to be 'medium-gray'. Got '%s'", statusCategory.ColorName) } } From 894a3fc53a76074b42952942161c814c5600ae46 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:04:11 +0200 Subject: [PATCH 105/189] Cloud/Status category: Add comments for usage example Related #577 --- .../main.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) rename cloud/examples/{getthestatuscategories => statuscategories}/main.go (65%) diff --git a/cloud/examples/getthestatuscategories/main.go b/cloud/examples/statuscategories/main.go similarity index 65% rename from cloud/examples/getthestatuscategories/main.go rename to cloud/examples/statuscategories/main.go index 02c343d..9a9f7f7 100644 --- a/cloud/examples/getthestatuscategories/main.go +++ b/cloud/examples/statuscategories/main.go @@ -2,14 +2,19 @@ package main import ( "context" - jira "github.com/andygrunwald/go-jira/v2/cloud" "log" + + jira "github.com/andygrunwald/go-jira/v2/cloud" ) func main() { + jiraClient, err := jira.NewClient("https://mattermost.atlassian.net/", nil) + if err != nil { + panic(err) + } - jiraClient, _ := jira.NewClient("https://mattermost.atlassian.net/", nil) - + // Showcase of StatusCategory.GetList: + // Getting all status categories categories, resp, err := jiraClient.StatusCategory.GetList(context.TODO()) if err != nil { log.Println(resp.StatusCode) @@ -20,6 +25,8 @@ func main() { log.Println(statusCategory) } + // Showcase of StatusCategory.Get + // Getting a single status category category, resp, err := jiraClient.StatusCategory.Get(context.TODO(), "1") if err != nil { log.Println(resp.StatusCode) From 4fec6ac43e9bd62047e7083735b3c9909ed85fb6 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:06:07 +0200 Subject: [PATCH 106/189] On premise/Status category: Add comments for usage example Related #577 --- .../main.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) rename onpremise/examples/{getthestatuscategories => statuscategories}/main.go (66%) diff --git a/onpremise/examples/getthestatuscategories/main.go b/onpremise/examples/statuscategories/main.go similarity index 66% rename from onpremise/examples/getthestatuscategories/main.go rename to onpremise/examples/statuscategories/main.go index a0a6565..364b7c4 100644 --- a/onpremise/examples/getthestatuscategories/main.go +++ b/onpremise/examples/statuscategories/main.go @@ -2,14 +2,19 @@ package main import ( "context" - jira "github.com/andygrunwald/go-jira/v2/onpremise" "log" + + jira "github.com/andygrunwald/go-jira/v2/onpremise" ) func main() { + jiraClient, err := jira.NewClient("https://issues.apache.org/jira/", nil) + if err != nil { + panic(err) + } - jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", nil) - + // Showcase of StatusCategory.GetList: + // Getting all status categories categories, resp, err := jiraClient.StatusCategory.GetList(context.TODO()) if err != nil { log.Println(resp.StatusCode) @@ -20,6 +25,8 @@ func main() { log.Println(statusCategory) } + // Showcase of StatusCategory.Get + // Getting a single status category category, resp, err := jiraClient.StatusCategory.Get(context.TODO(), "1") if err != nil { log.Println(resp.StatusCode) From 4f0fa86f19fdf836a25ce1b550620b888435764d Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:14:56 +0200 Subject: [PATCH 107/189] Cloud/Status category: fixed error message if no status category id is given --- cloud/statuscategory.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/statuscategory.go b/cloud/statuscategory.go index 7a33fa0..5918f7c 100644 --- a/cloud/statuscategory.go +++ b/cloud/statuscategory.go @@ -60,7 +60,7 @@ func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-idorkey-get func (s *StatusCategoryService) Get(ctx context.Context, statusCategoryID string) (*StatusCategory, *Response, error) { if statusCategoryID == "" { - return nil, nil, errors.New("jira: not status category set") + return nil, nil, errors.New("no status category id set") } apiEndpoint := fmt.Sprintf("/rest/api/3/statuscategory/%v", statusCategoryID) From 62ba32fd61571902d078d36b09bf3a23975fc0c9 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:15:27 +0200 Subject: [PATCH 108/189] Cloud/Status category: Added empty line to improve readability --- cloud/statuscategory_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud/statuscategory_test.go b/cloud/statuscategory_test.go index 91942a3..af7aab8 100644 --- a/cloud/statuscategory_test.go +++ b/cloud/statuscategory_test.go @@ -55,6 +55,7 @@ func TestStatusCategoryService_Get(t *testing.T) { statusCategory, _, err := testClient.StatusCategory.Get(context.Background(), "1") if err != nil { t.Errorf("Error given: %s", err) + } else if statusCategory == nil { t.Error("Expected status category. StatusCategory is nil") From f0fc73b7592eaa6d610ed8fc9dae3c82bd06b81b Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:16:01 +0200 Subject: [PATCH 109/189] On premise/Status category: Fixed godoc and links to Jira documentation --- onpremise/statuscategory.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/onpremise/statuscategory.go b/onpremise/statuscategory.go index bc8c84f..7553459 100644 --- a/onpremise/statuscategory.go +++ b/onpremise/statuscategory.go @@ -10,7 +10,6 @@ import ( // StatusCategoryService handles status categories for the Jira instance / API. // // Use it to obtain a list of all status categories and the details of a category. -// // Status categories provided a mechanism for categorizing statuses. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-workflow-status-categories/#api-group-workflow-status-categories @@ -34,9 +33,9 @@ const ( StatusCategoryUndefined = "undefined" ) -// GetList gets all status categories from Jira +// GetList returns a list of all status categories. // -// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-workflow-status-categories/#api-rest-api-2-statuscategory-get +// Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/statuscategory-getStatusCategories func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, *Response, error) { apiEndpoint := "/rest/api/2/statuscategory" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -49,18 +48,18 @@ func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, if err != nil { return nil, resp, NewJiraError(resp, err) } + return statusCategories, resp, nil } -// Get returns a status category. +// Get returns a full representation of the StatusCategory having the given id or key. // -// Status categories provided a mechanism for categorizing statuses. +// statusCategoryID represents the ID or key of the status category. // -// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-workflow-status-categories/#api-rest-api-2-statuscategory-idorkey-get +// Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/statuscategory-getStatusCategory func (s *StatusCategoryService) Get(ctx context.Context, statusCategoryID string) (*StatusCategory, *Response, error) { - if statusCategoryID == "" { - return nil, nil, errors.New("jira: not status category set") + return nil, nil, errors.New("no status category id set") } apiEndpoint := fmt.Sprintf("/rest/api/2/statuscategory/%v", statusCategoryID) From 1e4735ced86e0fe6da190432c13c45fbc260b22a Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:16:35 +0200 Subject: [PATCH 110/189] On premise/Status category: Added an additional test check --- onpremise/statuscategory_test.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/onpremise/statuscategory_test.go b/onpremise/statuscategory_test.go index cc2b9dc..be27db3 100644 --- a/onpremise/statuscategory_test.go +++ b/onpremise/statuscategory_test.go @@ -11,15 +11,16 @@ import ( func TestStatusCategoryService_GetList(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/statuscategory" + testAPIEndpoint := "/rest/api/2/statuscategory" raw, err := os.ReadFile("../testing/mock-data/all_statuscategories.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) @@ -27,6 +28,9 @@ func TestStatusCategoryService_GetList(t *testing.T) { if statusCategory == nil { t.Error("Expected statusCategory list. StatusCategory list is nil") } + if l := len(statusCategory); l != 4 { + t.Errorf("Expected 4 statusCategory list items. Got %d", l) + } if err != nil { t.Errorf("Error given: %s", err) } @@ -41,6 +45,7 @@ func TestStatusCategoryService_Get(t *testing.T) { if err != nil { t.Error(err.Error()) } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) testRequestURL(t, r, testAPIEndpoint) @@ -48,10 +53,14 @@ func TestStatusCategoryService_Get(t *testing.T) { }) statusCategory, _, err := testClient.StatusCategory.Get(context.Background(), "1") - if err != nil { t.Errorf("Error given: %s", err) + } else if statusCategory == nil { t.Error("Expected status category. StatusCategory is nil") + + // Checking testdata + } else if statusCategory.ColorName != "medium-gray" { + t.Errorf("Expected statusCategory.ColorName to be 'medium-gray'. Got '%s'", statusCategory.ColorName) } } From c361e092a39e6d2563a5c625a2c9c02dc1b8a357 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:19:58 +0200 Subject: [PATCH 111/189] CHANGELOG: Workflow status categories: Revisited and fully implemented for Cloud and On Premise (incl. examples) --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c87682b..335aaf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -349,9 +349,9 @@ client.Project.GetAll(ctx, &GetQueryOptions{}) * README: Fixed all (broken) links -### Bug Fixes +### API-Endpoints -* README: Fixed all (broken) links +* Workflow status categories: Revisited and fully implemented for Cloud and On Premise (incl. examples) ### Other From 469abf12a507317f276fd0c17b65b62550c0c76f Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:57:02 +0200 Subject: [PATCH 112/189] On premise/Authentication: Return error where possible --- onpremise/authentication.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/onpremise/authentication.go b/onpremise/authentication.go index 27bc8c4..96eeee1 100644 --- a/onpremise/authentication.go +++ b/onpremise/authentication.go @@ -73,17 +73,16 @@ func (s *AuthenticationService) AcquireSessionCookie(ctx context.Context, userna session := new(Session) resp, err := s.client.Do(req, session) - - if resp != nil { - session.Cookies = resp.Cookies() - } - if err != nil { - return false, fmt.Errorf("auth at Jira instance failed (HTTP(S) request). %s", err) + return false, fmt.Errorf("auth at Jira instance failed (HTTP(S) request). %w", err) } + if resp != nil && resp.StatusCode != 200 { return false, fmt.Errorf("auth at Jira instance failed (HTTP(S) request). Status code: %d", resp.StatusCode) } + if resp != nil { + session.Cookies = resp.Cookies() + } s.client.session = session s.authType = authTypeSession @@ -127,12 +126,12 @@ func (s *AuthenticationService) Logout(ctx context.Context) error { apiEndpoint := "rest/auth/1/session" req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { - return fmt.Errorf("creating the request to log the user out failed : %s", err) + return fmt.Errorf("creating the request to log the user out failed : %w", err) } resp, err := s.client.Do(req, nil) if err != nil { - return fmt.Errorf("error sending the logout request: %s", err) + return fmt.Errorf("error sending the logout request: %w", err) } defer resp.Body.Close() if resp.StatusCode != 204 { @@ -160,12 +159,12 @@ func (s *AuthenticationService) GetCurrentUser(ctx context.Context) (*Session, e apiEndpoint := "rest/auth/1/session" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { - return nil, fmt.Errorf("could not create request for getting user info : %s", err) + return nil, fmt.Errorf("could not create request for getting user info: %w", err) } resp, err := s.client.Do(req, nil) if err != nil { - return nil, fmt.Errorf("error sending request to get user info : %s", err) + return nil, fmt.Errorf("error sending request to get user info: %w", err) } defer resp.Body.Close() if resp.StatusCode != 200 { From 75eddf5684f34e4d6dfaf6a5e5931d018b7f10cb Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 11:58:45 +0200 Subject: [PATCH 113/189] On premise/Issue: Fix test data --- onpremise/issue_test.go | 113 ++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 57 deletions(-) diff --git a/onpremise/issue_test.go b/onpremise/issue_test.go index 88bdc20..36b6aa3 100644 --- a/onpremise/issue_test.go +++ b/onpremise/issue_test.go @@ -945,70 +945,69 @@ func TestIssueService_DoTransitionWithPayload(t *testing.T) { func TestIssueFields_TestMarshalJSON_PopulateUnknownsSuccess(t *testing.T) { data := `{ - "customfield_123":"test", - "description":"example bug report", - "project":{ - "self":"http://www.example.com/jira/rest/api/2/project/EX", + "customfield_123":"test", + "description":"example bug report", + "project":{ + "self":"http://www.example.com/jira/rest/api/2/project/EX", + "id":"10000", + "key":"EX", + "name":"Example", + "avatarUrls":{ + "48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000", + "24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000", + "16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000", + "32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000" + }, + "projectCategory":{ + "self":"http://www.example.com/jira/rest/api/2/projectCategory/10000", + "id":"10000", + "name":"FIRST", + "description":"First Project Category" + } + }, + "issuelinks":[ + { + "id":"10001", + "type":{ "id":"10000", - "key":"EX", - "name":"Example", - "avatarUrls":{ - "48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000", - "24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000", - "16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000", - "32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000" + "name":"Dependent", + "inward":"depends on", + "outward":"is depended by" }, - "projectCategory":{ - "self":"http://www.example.com/jira/rest/api/2/projectCategory/10000", - "id":"10000", - "name":"FIRST", - "description":"First Project Category" + "outwardIssue":{ + "id":"10004L", + "key":"PRJ-2", + "self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2", + "fields":{ + "status":{ + "iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png", + "name":"Open" + } + } } }, - "issuelinks":[ - { - "id":"10001", - "type":{ - "id":"10000", - "name":"Dependent", - "inward":"depends on", - "outward":"is depended by" - }, - "outwardIssue":{ - "id":"10004L", - "key":"PRJ-2", - "self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2", - "fields":{ - "status":{ - "iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png", - "name":"Open" - } - } - } + { + "id":"10002", + "type":{ + "id":"10000", + "name":"Dependent", + "inward":"depends on", + "outward":"is depended by" }, - { - "id":"10002", - "type":{ - "id":"10000", - "name":"Dependent", - "inward":"depends on", - "outward":"is depended by" - }, - "inwardIssue":{ - "id":"10004", - "key":"PRJ-3", - "self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3", - "fields":{ - "status":{ - "iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png", - "name":"Open" - } - } + "inwardIssue":{ + "id":"10004", + "key":"PRJ-3", + "self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3", + "fields":{ + "status":{ + "iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png", + "name":"Open" } } - ] - - }` + } + } + ] + }` i := new(IssueFields) err := json.Unmarshal([]byte(data), i) From 221b42e8ceb2df3fb94c2f0b72f88a78fe2fbbf3 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 12:01:55 +0200 Subject: [PATCH 114/189] Changelog: Internal: Replaced `io.ReadAll` and `json.Unmarshal` with `json.NewDecoder` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 335aaf3..0f5c8d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -357,6 +357,7 @@ client.Project.GetAll(ctx, &GetQueryOptions{}) * Replace all "GET", "POST", ... with http.MethodGet (and related) constants * Development: Added `make` commands to collect (unit) test coverage +* Internal: Replaced `io.ReadAll` and `json.Unmarshal` with `json.NewDecoder` ### Changes From 82afaac8f4dabbec2032c723f43668d9292ee244 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 12:16:41 +0200 Subject: [PATCH 115/189] Fix #579: Cloud/Authentication: Removed `BearerAuthTransport`, because it was a 100% duplicate of `PATAuthTransport` --- cloud/auth_transport_bearer.go | 41 ---------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 cloud/auth_transport_bearer.go diff --git a/cloud/auth_transport_bearer.go b/cloud/auth_transport_bearer.go deleted file mode 100644 index 9ff3f90..0000000 --- a/cloud/auth_transport_bearer.go +++ /dev/null @@ -1,41 +0,0 @@ -package cloud - -import ( - "fmt" - "net/http" -) - -// BearerAuthTransport is a http.RoundTripper that authenticates all requests -// using Jira's bearer (oauth 2.0 (3lo)) based authentication. -type BearerAuthTransport struct { - Token string - - // Transport is the underlying HTTP transport to use when making requests. - // It will default to http.DefaultTransport if nil. - Transport http.RoundTripper -} - -// RoundTrip implements the RoundTripper interface. We just add the -// bearer token and return the RoundTripper for this transport type. -func (t *BearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req2 := cloneRequest(req) // per RoundTripper contract - - req2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.Token)) - return t.transport().RoundTrip(req2) -} - -// Client returns an *http.Client that makes requests that are authenticated -// using HTTP Basic Authentication. This is a nice little bit of sugar -// so we can just get the client instead of creating the client in the calling code. -// If it's necessary to send more information on client init, the calling code can -// always skip this and set the transport itself. -func (t *BearerAuthTransport) Client() *http.Client { - return &http.Client{Transport: t} -} - -func (t *BearerAuthTransport) transport() http.RoundTripper { - if t.Transport != nil { - return t.Transport - } - return http.DefaultTransport -} From 4a88a05f6757dce8dd51f8783e6a5e375ed7c1d1 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 12:17:19 +0200 Subject: [PATCH 116/189] Fix #579: Cloud/Authentication: `PATAuthTransport` was renamed to `APITokenAuthTransport` --- ...s_token.go => auth_transport_api_token.go} | 24 ++++++++++--------- ...st.go => auth_transport_api_token_test.go} | 7 +++--- 2 files changed, 16 insertions(+), 15 deletions(-) rename cloud/{auth_transport_personal_access_token.go => auth_transport_api_token.go} (50%) rename cloud/{auth_transport_personal_access_token_test.go => auth_transport_api_token_test.go} (76%) diff --git a/cloud/auth_transport_personal_access_token.go b/cloud/auth_transport_api_token.go similarity index 50% rename from cloud/auth_transport_personal_access_token.go rename to cloud/auth_transport_api_token.go index 2661ac0..fe0fbb0 100644 --- a/cloud/auth_transport_personal_access_token.go +++ b/cloud/auth_transport_api_token.go @@ -5,11 +5,13 @@ import ( "net/http" ) -// PATAuthTransport is an http.RoundTripper that authenticates all requests -// using the Personal Access Token specified. -// See here for more info: https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html -type PATAuthTransport struct { - // Token is the key that was provided by Jira when creating the Personal Access Token. +// APITokenAuthTransport is an http.RoundTripper that authenticates all requests +// using a Personal API Token. +// +// Jira docs: https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/ +// Create a token: https://id.atlassian.com/manage-profile/security/api-tokens +type APITokenAuthTransport struct { + // Token is the API key. Token string // Transport is the underlying HTTP transport to use when making requests. @@ -17,9 +19,9 @@ type PATAuthTransport struct { Transport http.RoundTripper } -// RoundTrip implements the RoundTripper interface. We just add the -// basic auth and return the RoundTripper for this transport type. -func (t *PATAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { +// RoundTrip implements the RoundTripper interface. We just add the +// API token header and return the RoundTripper for this transport type. +func (t *APITokenAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { req2 := cloneRequest(req) // per RoundTripper contract req2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.Token)) @@ -27,15 +29,15 @@ func (t *PATAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) } // Client returns an *http.Client that makes requests that are authenticated -// using HTTP Basic Authentication. This is a nice little bit of sugar +// using the API token. This is a nice little bit of sugar // so we can just get the client instead of creating the client in the calling code. // If it's necessary to send more information on client init, the calling code can // always skip this and set the transport itself. -func (t *PATAuthTransport) Client() *http.Client { +func (t *APITokenAuthTransport) Client() *http.Client { return &http.Client{Transport: t} } -func (t *PATAuthTransport) transport() http.RoundTripper { +func (t *APITokenAuthTransport) transport() http.RoundTripper { if t.Transport != nil { return t.Transport } diff --git a/cloud/auth_transport_personal_access_token_test.go b/cloud/auth_transport_api_token_test.go similarity index 76% rename from cloud/auth_transport_personal_access_token_test.go rename to cloud/auth_transport_api_token_test.go index d240315..1f42f55 100644 --- a/cloud/auth_transport_personal_access_token_test.go +++ b/cloud/auth_transport_api_token_test.go @@ -6,13 +6,13 @@ import ( "testing" ) -func TestPATAuthTransport_HeaderContainsAuth(t *testing.T) { +func TestAPITokenAuthTransport_HeaderContainsAuth(t *testing.T) { setup() defer teardown() - token := "shhh, it's a token" + token := "shhh, it's an API token" - patTransport := &PATAuthTransport{ + patTransport := &APITokenAuthTransport{ Token: token, } @@ -26,5 +26,4 @@ func TestPATAuthTransport_HeaderContainsAuth(t *testing.T) { client, _ := NewClient(testServer.URL, patTransport.Client()) client.User.GetSelf(context.Background()) - } From 6f4aa977aebe3555805d064018a806e3a5020d00 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 12:17:47 +0200 Subject: [PATCH 117/189] Fix #579: Update changelog --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f5c8d5..1c6ab24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -326,6 +326,37 @@ After: client.Project.GetAll(ctx, &GetQueryOptions{}) ``` +#### Cloud/Authentication: `BearerAuthTransport` removed, `PATAuthTransport` renamed + +If you used `BearerAuthTransport` or `PATAuthTransport` for authentication, please replace it with `APITokenAuthTransport`. + +Before: + +```go +tp := jira.BearerAuthTransport{ + Token: "token", +} +client, err := jira.NewClient("https://...", tp.Client()) +``` + +or + +```go +tp := jira.PATAuthTransport{ + Token: "token", +} +client, err := jira.NewClient("https://...", tp.Client()) +``` + +After: + +```go +tp := jira.APITokenAuthTransport{ + Token: "token", +} +client, err := jira.NewClient("https://...", tp.Client()) +``` + ### Breaking changes * Jira On-Premise and Jira Cloud have now different clients, because the API differs @@ -338,6 +369,8 @@ client.Project.GetAll(ctx, &GetQueryOptions{}) * `Issue.Update` has been removed and `Issue.UpdateWithOptions` has been renamed to `Issue.Update` * `Issue.GetCreateMeta` has been removed and `Issue.GetCreateMetaWithOptions` has been renamed to `Issue.GetCreateMeta` * `Project.GetList` has been removed and `Project.ListWithOptions` has been renamed to `Project.GetAll` +* Cloud/Authentication: Removed `BearerAuthTransport`, because it was a 100% duplicate of `PATAuthTransport` +* Cloud/Authentication: `PATAuthTransport` was renamed to `APITokenAuthTransport` ### Features From 641e89f1ec2ed8e9b8e331c851ba6ddb48f43d09 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 14:01:59 +0200 Subject: [PATCH 118/189] Cloud/Authentication: Removed `APITokenAuthTransport`, renamed `BasicAuthTransport.Password` to `BasicAuthTransport.APIToken` Fix #579 --- CHANGELOG.md | 36 +++++++++++--- README.md | 13 ++--- cloud/auth_transport_api_token.go | 45 ----------------- cloud/auth_transport_api_token_test.go | 29 ----------- ..._basic.go => auth_transport_basic_auth.go} | 11 +++-- ...t.go => auth_transport_basic_auth_test.go} | 8 +-- cloud/examples/basic_auth/main.go | 31 ++++++++++++ cloud/examples/basicauth/main.go | 49 ------------------- 8 files changed, 79 insertions(+), 143 deletions(-) delete mode 100644 cloud/auth_transport_api_token.go delete mode 100644 cloud/auth_transport_api_token_test.go rename cloud/{auth_transport_basic.go => auth_transport_basic_auth.go} (77%) rename cloud/{auth_transport_basic_test.go => auth_transport_basic_auth_test.go} (91%) create mode 100644 cloud/examples/basic_auth/main.go delete mode 100644 cloud/examples/basicauth/main.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c6ab24..211ddbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -326,9 +326,9 @@ After: client.Project.GetAll(ctx, &GetQueryOptions{}) ``` -#### Cloud/Authentication: `BearerAuthTransport` removed, `PATAuthTransport` renamed +#### Cloud/Authentication: `BearerAuthTransport` removed, `PATAuthTransport` removed -If you used `BearerAuthTransport` or `PATAuthTransport` for authentication, please replace it with `APITokenAuthTransport`. +If you used `BearerAuthTransport` or `PATAuthTransport` for authentication, please replace it with `BasicAuthTransport`. Before: @@ -351,8 +351,31 @@ client, err := jira.NewClient("https://...", tp.Client()) After: ```go -tp := jira.APITokenAuthTransport{ - Token: "token", +tp := jira.BasicAuthTransport{ + Username: "username", + APIToken: "token", +} +client, err := jira.NewClient("https://...", tp.Client()) +``` + +#### Cloud/Authentication: `BasicAuthTransport.Password` was renamed to `BasicAuthTransport.APIToken` + +Before: + +```go +tp := jira.BasicAuthTransport{ + Username: "username", + Password: "token", +} +client, err := jira.NewClient("https://...", tp.Client()) +``` + +After: + +```go +tp := jira.BasicAuthTransport{ + Username: "username", + APIToken: "token", } client, err := jira.NewClient("https://...", tp.Client()) ``` @@ -369,8 +392,9 @@ client, err := jira.NewClient("https://...", tp.Client()) * `Issue.Update` has been removed and `Issue.UpdateWithOptions` has been renamed to `Issue.Update` * `Issue.GetCreateMeta` has been removed and `Issue.GetCreateMetaWithOptions` has been renamed to `Issue.GetCreateMeta` * `Project.GetList` has been removed and `Project.ListWithOptions` has been renamed to `Project.GetAll` -* Cloud/Authentication: Removed `BearerAuthTransport`, because it was a 100% duplicate of `PATAuthTransport` -* Cloud/Authentication: `PATAuthTransport` was renamed to `APITokenAuthTransport` +* Cloud/Authentication: Removed `BearerAuthTransport`, because it was a (kind of) duplicate of `BasicAuthTransport` +* Cloud/Authentication: Removed `PATAuthTransport`, because it was a (kind of) duplicate of `BasicAuthTransport` +* Cloud/Authentication: `BasicAuthTransport.Password` was renamed to `BasicAuthTransport.APIToken` ### Features diff --git a/README.md b/README.md index ce1c78c..d11bfb5 100644 --- a/README.md +++ b/README.md @@ -100,20 +100,21 @@ For convenience, capability for basic and cookie-based authentication is include Token-based authentication uses the basic authentication scheme, with a user-generated API token in place of a user's password. You can generate a token for your user [here](https://id.atlassian.com/manage-profile/security/api-tokens). Additional information about Atlassian Cloud API tokens can be found [here](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/). -A more thorough, [runnable example](cloud/examples/basicauth/main.go) is provided in the examples directory. +A more thorough, [runnable example](cloud/examples/basic_auth/main.go) is provided in the examples directory. ```go func main() { tp := jira.BasicAuthTransport{ - Username: "username", - Password: "token", + Username: "", + APIToken: "", } - client, err := jira.NewClient(tp.Client(), "https://my.jira.com") + client, err := jira.NewClient("https://my.jira.com", tp.Client()) - u, _, err := client.User.Get("some_user") + u, _, err = client.User.GetCurrentUser(context.Background()) - fmt.Printf("\nEmail: %v\nSuccess!\n", u.EmailAddress) + fmt.Printf("Email: %v\n", u.EmailAddress) + fmt.Println("Success!") } ``` diff --git a/cloud/auth_transport_api_token.go b/cloud/auth_transport_api_token.go deleted file mode 100644 index fe0fbb0..0000000 --- a/cloud/auth_transport_api_token.go +++ /dev/null @@ -1,45 +0,0 @@ -package cloud - -import ( - "fmt" - "net/http" -) - -// APITokenAuthTransport is an http.RoundTripper that authenticates all requests -// using a Personal API Token. -// -// Jira docs: https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/ -// Create a token: https://id.atlassian.com/manage-profile/security/api-tokens -type APITokenAuthTransport struct { - // Token is the API key. - Token string - - // Transport is the underlying HTTP transport to use when making requests. - // It will default to http.DefaultTransport if nil. - Transport http.RoundTripper -} - -// RoundTrip implements the RoundTripper interface. We just add the -// API token header and return the RoundTripper for this transport type. -func (t *APITokenAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req2 := cloneRequest(req) // per RoundTripper contract - - req2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.Token)) - return t.transport().RoundTrip(req2) -} - -// Client returns an *http.Client that makes requests that are authenticated -// using the API token. This is a nice little bit of sugar -// so we can just get the client instead of creating the client in the calling code. -// If it's necessary to send more information on client init, the calling code can -// always skip this and set the transport itself. -func (t *APITokenAuthTransport) Client() *http.Client { - return &http.Client{Transport: t} -} - -func (t *APITokenAuthTransport) transport() http.RoundTripper { - if t.Transport != nil { - return t.Transport - } - return http.DefaultTransport -} diff --git a/cloud/auth_transport_api_token_test.go b/cloud/auth_transport_api_token_test.go deleted file mode 100644 index 1f42f55..0000000 --- a/cloud/auth_transport_api_token_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package cloud - -import ( - "context" - "net/http" - "testing" -) - -func TestAPITokenAuthTransport_HeaderContainsAuth(t *testing.T) { - setup() - defer teardown() - - token := "shhh, it's an API token" - - patTransport := &APITokenAuthTransport{ - Token: token, - } - - testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - val := r.Header.Get("Authorization") - expected := "Bearer " + token - if val != expected { - t.Errorf("request does not contain bearer token in the Authorization header.") - } - }) - - client, _ := NewClient(testServer.URL, patTransport.Client()) - client.User.GetSelf(context.Background()) -} diff --git a/cloud/auth_transport_basic.go b/cloud/auth_transport_basic_auth.go similarity index 77% rename from cloud/auth_transport_basic.go rename to cloud/auth_transport_basic_auth.go index b0e3c4c..fb8a1cb 100644 --- a/cloud/auth_transport_basic.go +++ b/cloud/auth_transport_basic_auth.go @@ -3,10 +3,13 @@ package cloud import "net/http" // BasicAuthTransport is an http.RoundTripper that authenticates all requests -// using HTTP Basic Authentication with the provided username and password. +// using HTTP Basic Authentication with the provided username and a Personal API Token. +// +// Jira docs: https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/ +// Create a token: https://id.atlassian.com/manage-profile/security/api-tokens type BasicAuthTransport struct { Username string - Password string + APIToken string // Transport is the underlying HTTP transport to use when making requests. // It will default to http.DefaultTransport if nil. @@ -14,11 +17,11 @@ type BasicAuthTransport struct { } // RoundTrip implements the RoundTripper interface. We just add the -// basic auth and return the RoundTripper for this transport type. +// basic auth information and return the RoundTripper for this transport type. func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { req2 := cloneRequest(req) // per RoundTripper contract - req2.SetBasicAuth(t.Username, t.Password) + req2.SetBasicAuth(t.Username, t.APIToken) return t.transport().RoundTrip(req2) } diff --git a/cloud/auth_transport_basic_test.go b/cloud/auth_transport_basic_auth_test.go similarity index 91% rename from cloud/auth_transport_basic_test.go rename to cloud/auth_transport_basic_auth_test.go index 421705e..0ef1c85 100644 --- a/cloud/auth_transport_basic_test.go +++ b/cloud/auth_transport_basic_auth_test.go @@ -10,7 +10,7 @@ func TestBasicAuthTransport(t *testing.T) { setup() defer teardown() - username, password := "username", "password" + username, apiToken := "username", "api_token" testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { u, p, ok := r.BasicAuth() @@ -20,14 +20,14 @@ func TestBasicAuthTransport(t *testing.T) { if u != username { t.Errorf("request contained basic auth username %q, want %q", u, username) } - if p != password { - t.Errorf("request contained basic auth password %q, want %q", p, password) + if p != apiToken { + t.Errorf("request contained basic auth password %q, want %q", p, apiToken) } }) tp := &BasicAuthTransport{ Username: username, - Password: password, + APIToken: apiToken, } basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) diff --git a/cloud/examples/basic_auth/main.go b/cloud/examples/basic_auth/main.go new file mode 100644 index 0000000..bafa6a0 --- /dev/null +++ b/cloud/examples/basic_auth/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "context" + "fmt" + + jira "github.com/andygrunwald/go-jira/v2/cloud" +) + +func main() { + jiraURL := "https://go-jira-opensource.atlassian.net/" + + // Jira docs: https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/ + // Create a new API token: https://id.atlassian.com/manage-profile/security/api-tokens + tp := jira.BasicAuthTransport{ + Username: "", + APIToken: "", + } + client, err := jira.NewClient(jiraURL, tp.Client()) + if err != nil { + panic(err) + } + + u, _, err := client.User.GetCurrentUser(context.Background()) + if err != nil { + panic(err) + } + + fmt.Printf("Email: %v\n", u.EmailAddress) + fmt.Println("Success!") +} diff --git a/cloud/examples/basicauth/main.go b/cloud/examples/basicauth/main.go deleted file mode 100644 index b04888a..0000000 --- a/cloud/examples/basicauth/main.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "bufio" - "context" - "fmt" - "os" - "strings" - "syscall" - - "golang.org/x/term" - - jira "github.com/andygrunwald/go-jira/v2/cloud" -) - -func main() { - r := bufio.NewReader(os.Stdin) - - fmt.Print("Jira URL: ") - jiraURL, _ := r.ReadString('\n') - - fmt.Print("Jira Username: ") - username, _ := r.ReadString('\n') - - fmt.Print("Jira Password: ") - bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) - password := string(bytePassword) - - tp := jira.BasicAuthTransport{ - Username: strings.TrimSpace(username), - Password: strings.TrimSpace(password), - } - - client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) - if err != nil { - fmt.Printf("\nerror: %v\n", err) - return - } - - u, _, err := client.User.Get(context.Background(), "admin") - - if err != nil { - fmt.Printf("\nerror: %v\n", err) - return - } - - fmt.Printf("\nEmail: %v\nSuccess!\n", u.EmailAddress) - -} From fb6a19634d0dbb990554ecd969807147549d4884 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 14:07:07 +0200 Subject: [PATCH 119/189] Fix unit tests --- cloud/examples/addlabel/main.go | 2 +- cloud/examples/create/main.go | 2 +- cloud/examples/createwithcustomfields/main.go | 2 +- cloud/examples/renderedfields/main.go | 2 +- cloud/examples/searchpages/main.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cloud/examples/addlabel/main.go b/cloud/examples/addlabel/main.go index 4ece27f..dc6a397 100644 --- a/cloud/examples/addlabel/main.go +++ b/cloud/examples/addlabel/main.go @@ -36,7 +36,7 @@ func main() { tp := jira.BasicAuthTransport{ Username: strings.TrimSpace(username), - Password: strings.TrimSpace(password), + APIToken: strings.TrimSpace(password), } client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) diff --git a/cloud/examples/create/main.go b/cloud/examples/create/main.go index ecfdd7c..df17533 100644 --- a/cloud/examples/create/main.go +++ b/cloud/examples/create/main.go @@ -27,7 +27,7 @@ func main() { tp := jira.BasicAuthTransport{ Username: strings.TrimSpace(username), - Password: strings.TrimSpace(password), + APIToken: strings.TrimSpace(password), } client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) diff --git a/cloud/examples/createwithcustomfields/main.go b/cloud/examples/createwithcustomfields/main.go index 22f531f..4c1b035 100644 --- a/cloud/examples/createwithcustomfields/main.go +++ b/cloud/examples/createwithcustomfields/main.go @@ -34,7 +34,7 @@ func main() { tp := jira.BasicAuthTransport{ Username: strings.TrimSpace(username), - Password: strings.TrimSpace(password), + APIToken: strings.TrimSpace(password), } client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) diff --git a/cloud/examples/renderedfields/main.go b/cloud/examples/renderedfields/main.go index 1fc5de5..b9ec26b 100644 --- a/cloud/examples/renderedfields/main.go +++ b/cloud/examples/renderedfields/main.go @@ -39,7 +39,7 @@ func main() { ba := jira.BasicAuthTransport{ Username: strings.TrimSpace(username), - Password: strings.TrimSpace(password), + APIToken: strings.TrimSpace(password), } tp = ba.Client() } diff --git a/cloud/examples/searchpages/main.go b/cloud/examples/searchpages/main.go index 56ebebb..d3a1198 100644 --- a/cloud/examples/searchpages/main.go +++ b/cloud/examples/searchpages/main.go @@ -32,7 +32,7 @@ func main() { tp := jira.BasicAuthTransport{ Username: strings.TrimSpace(username), - Password: strings.TrimSpace(password), + APIToken: strings.TrimSpace(password), } client, err := jira.NewClient(strings.TrimSpace(jiraURL), tp.Client()) From d86ceae7b1095c323eff4a4acd19462c32d7c987 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 14:09:08 +0200 Subject: [PATCH 120/189] Cloud/Examples: Fix "GetSelf" method to get the current user --- cloud/examples/basic_auth/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/examples/basic_auth/main.go b/cloud/examples/basic_auth/main.go index bafa6a0..ab37284 100644 --- a/cloud/examples/basic_auth/main.go +++ b/cloud/examples/basic_auth/main.go @@ -21,7 +21,7 @@ func main() { panic(err) } - u, _, err := client.User.GetCurrentUser(context.Background()) + u, _, err := client.User.GetSelf(context.Background()) if err != nil { panic(err) } From 4bf851e00cb8730b5e42ded1f92ec4e9fc4bcabb Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 14:20:16 +0200 Subject: [PATCH 121/189] Fix #578: Remove `CookieAuthTransport` and `AuthenticationService` The main rational: This is not supported by Jira cloud offering. OAuth, BasicAuth, JWT are supported. --- cloud/auth_transport_cookie.go | 107 --------- cloud/auth_transport_cookie_test.go | 120 ----------- cloud/authentication.go | 181 ---------------- cloud/authentication_test.go | 322 ---------------------------- cloud/jira.go | 52 ----- cloud/jira_test.go | 94 -------- 6 files changed, 876 deletions(-) delete mode 100644 cloud/auth_transport_cookie.go delete mode 100644 cloud/auth_transport_cookie_test.go delete mode 100644 cloud/authentication.go delete mode 100644 cloud/authentication_test.go diff --git a/cloud/auth_transport_cookie.go b/cloud/auth_transport_cookie.go deleted file mode 100644 index 26565b5..0000000 --- a/cloud/auth_transport_cookie.go +++ /dev/null @@ -1,107 +0,0 @@ -package cloud - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "time" -) - -// CookieAuthTransport is an http.RoundTripper that authenticates all requests -// using Jira's cookie-based authentication. -// -// Note that it is generally preferable to use HTTP BASIC authentication with the REST API. -// However, this resource may be used to mimic the behaviour of Jira's log-in page (e.g. to display log-in errors to a user). -// -// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session -type CookieAuthTransport struct { - Username string - Password string - AuthURL string - - // SessionObject is the authenticated cookie string.s - // It's passed in each call to prove the client is authenticated. - SessionObject []*http.Cookie - - // Transport is the underlying HTTP transport to use when making requests. - // It will default to http.DefaultTransport if nil. - Transport http.RoundTripper -} - -// RoundTrip adds the session object to the request. -func (t *CookieAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { - if t.SessionObject == nil { - err := t.setSessionObject() - if err != nil { - return nil, fmt.Errorf("cookieauth: no session object has been set: %w", err) - } - } - - req2 := cloneRequest(req) // per RoundTripper contract - for _, cookie := range t.SessionObject { - // Don't add an empty value cookie to the request - if cookie.Value != "" { - req2.AddCookie(cookie) - } - } - - return t.transport().RoundTrip(req2) -} - -// Client returns an *http.Client that makes requests that are authenticated -// using cookie authentication -func (t *CookieAuthTransport) Client() *http.Client { - return &http.Client{Transport: t} -} - -// setSessionObject attempts to authenticate the user and set -// the session object (e.g. cookie) -func (t *CookieAuthTransport) setSessionObject() error { - req, err := t.buildAuthRequest() - if err != nil { - return err - } - - var authClient = &http.Client{ - Timeout: time.Second * 60, - } - resp, err := authClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - t.SessionObject = resp.Cookies() - return nil -} - -// getAuthRequest assembles the request to get the authenticated cookie -func (t *CookieAuthTransport) buildAuthRequest() (*http.Request, error) { - body := struct { - Username string `json:"username"` - Password string `json:"password"` - }{ - t.Username, - t.Password, - } - - b := new(bytes.Buffer) - json.NewEncoder(b).Encode(body) - - // TODO Use a context here - req, err := http.NewRequest(http.MethodPost, t.AuthURL, b) - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", "application/json") - return req, nil -} - -func (t *CookieAuthTransport) transport() http.RoundTripper { - if t.Transport != nil { - return t.Transport - } - return http.DefaultTransport -} diff --git a/cloud/auth_transport_cookie_test.go b/cloud/auth_transport_cookie_test.go deleted file mode 100644 index 1ce4340..0000000 --- a/cloud/auth_transport_cookie_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package cloud - -import ( - "context" - "net/http" - "net/http/httptest" - "testing" -) - -// Test that the cookie in the transport is the cookie returned in the header -func TestCookieAuthTransport_SessionObject_Exists(t *testing.T) { - setup() - defer teardown() - - testCookie := &http.Cookie{Name: "test", Value: "test"} - - testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - cookies := r.Cookies() - - if len(cookies) < 1 { - t.Errorf("No cookies set") - } - - if cookies[0].Name != testCookie.Name { - t.Errorf("Cookie names don't match, expected %v, got %v", testCookie.Name, cookies[0].Name) - } - - if cookies[0].Value != testCookie.Value { - t.Errorf("Cookie values don't match, expected %v, got %v", testCookie.Value, cookies[0].Value) - } - }) - - tp := &CookieAuthTransport{ - Username: "username", - Password: "password", - AuthURL: "https://some.jira.com/rest/auth/1/session", - SessionObject: []*http.Cookie{testCookie}, - } - - basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) - req, _ := basicAuthClient.NewRequest(context.Background(), http.MethodGet, ".", nil) - basicAuthClient.Do(req, nil) -} - -// Test that an empty cookie in the transport is not returned in the header -func TestCookieAuthTransport_SessionObject_ExistsWithEmptyCookie(t *testing.T) { - setup() - defer teardown() - - emptyCookie := &http.Cookie{Name: "empty_cookie", Value: ""} - testCookie := &http.Cookie{Name: "test", Value: "test"} - - testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - cookies := r.Cookies() - - if len(cookies) > 1 { - t.Errorf("The empty cookie should not have been added") - } - - if cookies[0].Name != testCookie.Name { - t.Errorf("Cookie names don't match, expected %v, got %v", testCookie.Name, cookies[0].Name) - } - - if cookies[0].Value != testCookie.Value { - t.Errorf("Cookie values don't match, expected %v, got %v", testCookie.Value, cookies[0].Value) - } - }) - - tp := &CookieAuthTransport{ - Username: "username", - Password: "password", - AuthURL: "https://some.jira.com/rest/auth/1/session", - SessionObject: []*http.Cookie{emptyCookie, testCookie}, - } - - basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) - req, _ := basicAuthClient.NewRequest(context.Background(), http.MethodGet, ".", nil) - basicAuthClient.Do(req, nil) -} - -// Test that if no cookie is in the transport, it checks for a cookie -func TestCookieAuthTransport_SessionObject_DoesNotExist(t *testing.T) { - setup() - defer teardown() - - testCookie := &http.Cookie{Name: "does_not_exist", Value: "does_not_exist"} - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - http.SetCookie(w, testCookie) - w.Write([]byte(`OK`)) - })) - defer ts.Close() - - testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - cookies := r.Cookies() - - if len(cookies) < 1 { - t.Errorf("No cookies set") - } - - if cookies[0].Name != testCookie.Name { - t.Errorf("Cookie names don't match, expected %v, got %v", testCookie.Name, cookies[0].Name) - } - - if cookies[0].Value != testCookie.Value { - t.Errorf("Cookie values don't match, expected %v, got %v", testCookie.Value, cookies[0].Value) - } - }) - - tp := &CookieAuthTransport{ - Username: "username", - Password: "password", - AuthURL: ts.URL, - } - - basicAuthClient, _ := NewClient(testServer.URL, tp.Client()) - req, _ := basicAuthClient.NewRequest(context.Background(), http.MethodGet, ".", nil) - basicAuthClient.Do(req, nil) -} diff --git a/cloud/authentication.go b/cloud/authentication.go deleted file mode 100644 index e730209..0000000 --- a/cloud/authentication.go +++ /dev/null @@ -1,181 +0,0 @@ -package cloud - -import ( - "context" - "encoding/json" - "fmt" - "net/http" -) - -const ( - // HTTP Basic Authentication - authTypeBasic = 1 - // HTTP Session Authentication - authTypeSession = 2 -) - -// AuthenticationService handles authentication for the Jira instance / API. -// -// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#authentication -type AuthenticationService struct { - client *Client - - // Authentication type - authType int - - // Basic auth username - username string - - // Basic auth password - password string -} - -// Session represents a Session JSON response by the Jira API. -type Session struct { - Self string `json:"self,omitempty"` - Name string `json:"name,omitempty"` - Session struct { - Name string `json:"name"` - Value string `json:"value"` - } `json:"session,omitempty"` - LoginInfo struct { - FailedLoginCount int `json:"failedLoginCount"` - LoginCount int `json:"loginCount"` - LastFailedLoginTime string `json:"lastFailedLoginTime"` - PreviousLoginTime string `json:"previousLoginTime"` - } `json:"loginInfo"` - Cookies []*http.Cookie -} - -// AcquireSessionCookie creates a new session for a user in Jira. -// Once a session has been successfully created it can be used to access any of Jira's remote APIs and also the web UI by passing the appropriate HTTP Cookie header. -// The header will by automatically applied to every API request. -// Note that it is generally preferrable to use HTTP BASIC authentication with the REST API. -// However, this resource may be used to mimic the behaviour of Jira's log-in page (e.g. to display log-in errors to a user). -// -// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session -// -// Deprecated: Use CookieAuthTransport instead -func (s *AuthenticationService) AcquireSessionCookie(ctx context.Context, username, password string) (bool, error) { - apiEndpoint := "rest/auth/1/session" - body := struct { - Username string `json:"username"` - Password string `json:"password"` - }{ - username, - password, - } - - req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, body) - if err != nil { - return false, err - } - - session := new(Session) - resp, err := s.client.Do(req, session) - if err != nil { - return false, fmt.Errorf("auth at Jira instance failed (HTTP(S) request). %w", err) - } - - if resp != nil && resp.StatusCode != 200 { - return false, fmt.Errorf("auth at Jira instance failed (HTTP(S) request). Status code: %d", resp.StatusCode) - } - if resp != nil { - session.Cookies = resp.Cookies() - } - - s.client.session = session - s.authType = authTypeSession - - return true, nil -} - -// SetBasicAuth sets username and password for the basic auth against the Jira instance. -// -// Deprecated: Use BasicAuthTransport instead -func (s *AuthenticationService) SetBasicAuth(username, password string) { - s.username = username - s.password = password - s.authType = authTypeBasic -} - -// Authenticated reports if the current Client has authentication details for Jira -func (s *AuthenticationService) Authenticated() bool { - if s != nil { - if s.authType == authTypeSession { - return s.client.session != nil - } else if s.authType == authTypeBasic { - return s.username != "" - } - - } - return false -} - -// Logout logs out the current user that has been authenticated and the session in the client is destroyed. -// -// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session -// -// Deprecated: Use CookieAuthTransport to create base client. Logging out is as simple as not using the -// client anymore -func (s *AuthenticationService) Logout(ctx context.Context) error { - if s.authType != authTypeSession || s.client.session == nil { - return fmt.Errorf("no user is authenticated") - } - - apiEndpoint := "rest/auth/1/session" - req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) - if err != nil { - return fmt.Errorf("creating the request to log the user out failed : %w", err) - } - - resp, err := s.client.Do(req, nil) - if err != nil { - return fmt.Errorf("error sending the logout request: %w", err) - } - defer resp.Body.Close() - if resp.StatusCode != 204 { - return fmt.Errorf("the logout was unsuccessful with status %d", resp.StatusCode) - } - - // If logout successful, delete session - s.client.session = nil - - return nil - -} - -// GetCurrentUser gets the details of the current user. -// -// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session -func (s *AuthenticationService) GetCurrentUser(ctx context.Context) (*Session, error) { - if s == nil { - return nil, fmt.Errorf("authentication Service is not instantiated") - } - if s.authType != authTypeSession || s.client.session == nil { - return nil, fmt.Errorf("no user is authenticated yet") - } - - apiEndpoint := "rest/auth/1/session" - req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) - if err != nil { - return nil, fmt.Errorf("could not create request for getting user info: %w", err) - } - - resp, err := s.client.Do(req, nil) - if err != nil { - return nil, fmt.Errorf("error sending request to get user info: %w", err) - } - defer resp.Body.Close() - if resp.StatusCode != 200 { - return nil, fmt.Errorf("getting user info failed with status : %d", resp.StatusCode) - } - - ret := new(Session) - err = json.NewDecoder(resp.Body).Decode(&ret) - if err != nil { - return nil, err - } - - return ret, nil -} diff --git a/cloud/authentication_test.go b/cloud/authentication_test.go deleted file mode 100644 index 47cf5e4..0000000 --- a/cloud/authentication_test.go +++ /dev/null @@ -1,322 +0,0 @@ -package cloud - -import ( - "bytes" - "context" - "fmt" - "io" - "net/http" - "reflect" - "testing" -) - -func TestAuthenticationService_AcquireSessionCookie_Failure(t *testing.T) { - setup() - defer teardown() - testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, http.MethodPost) - testRequestURL(t, r, "/rest/auth/1/session") - b, err := io.ReadAll(r.Body) - if err != nil { - t.Errorf("Error in read body: %s", err) - } - if !bytes.Contains(b, []byte(`"username":"foo"`)) { - t.Error("No username found") - } - if !bytes.Contains(b, []byte(`"password":"bar"`)) { - t.Error("No password found") - } - - // Emulate error - w.WriteHeader(http.StatusInternalServerError) - }) - - res, err := testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") - if err == nil { - t.Errorf("Expected error, but no error given") - } - if res == true { - t.Error("Expected error, but result was true") - } - - if testClient.Authentication.Authenticated() != false { - t.Error("Expected false, but result was true") - } -} - -func TestAuthenticationService_AcquireSessionCookie_Success(t *testing.T) { - setup() - defer teardown() - testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, http.MethodPost) - testRequestURL(t, r, "/rest/auth/1/session") - b, err := io.ReadAll(r.Body) - if err != nil { - t.Errorf("Error in read body: %s", err) - } - if !bytes.Contains(b, []byte(`"username":"foo"`)) { - t.Error("No username found") - } - if !bytes.Contains(b, []byte(`"password":"bar"`)) { - t.Error("No password found") - } - - fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) - }) - - res, err := testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") - if err != nil { - t.Errorf("No error expected. Got %s", err) - } - if res == false { - t.Error("Expected result was true. Got false") - } - - if testClient.Authentication.Authenticated() != true { - t.Error("Expected true, but result was false") - } - - if testClient.Authentication.authType != authTypeSession { - t.Errorf("Expected authType %d. Got %d", authTypeSession, testClient.Authentication.authType) - } -} - -func TestAuthenticationService_SetBasicAuth(t *testing.T) { - setup() - defer teardown() - - testClient.Authentication.SetBasicAuth("test-user", "test-password") - - if testClient.Authentication.username != "test-user" { - t.Errorf("Expected username test-user. Got %s", testClient.Authentication.username) - } - - if testClient.Authentication.password != "test-password" { - t.Errorf("Expected password test-password. Got %s", testClient.Authentication.password) - } - - if testClient.Authentication.authType != authTypeBasic { - t.Errorf("Expected authType %d. Got %d", authTypeBasic, testClient.Authentication.authType) - } -} - -func TestAuthenticationService_Authenticated(t *testing.T) { - // Skip setup() because we don't want a fully setup client - testClient = new(Client) - - // Test before we've attempted to authenticate - if testClient.Authentication.Authenticated() != false { - t.Error("Expected false, but result was true") - } -} - -func TestAuthenticationService_Authenticated_WithBasicAuth(t *testing.T) { - setup() - defer teardown() - - testClient.Authentication.SetBasicAuth("test-user", "test-password") - - // Test before we've attempted to authenticate - if testClient.Authentication.Authenticated() != true { - t.Error("Expected true, but result was false") - } -} - -func TestAuthenticationService_Authenticated_WithBasicAuthButNoUsername(t *testing.T) { - setup() - defer teardown() - - testClient.Authentication.SetBasicAuth("", "test-password") - - // Test before we've attempted to authenticate - if testClient.Authentication.Authenticated() != false { - t.Error("Expected false, but result was true") - } -} - -func TestAuthenticationService_GetUserInfo_AccessForbidden_Fail(t *testing.T) { - setup() - defer teardown() - testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - testMethod(t, r, http.MethodPost) - testRequestURL(t, r, "/rest/auth/1/session") - b, err := io.ReadAll(r.Body) - if err != nil { - t.Errorf("Error in read body: %s", err) - } - if !bytes.Contains(b, []byte(`"username":"foo"`)) { - t.Error("No username found") - } - if !bytes.Contains(b, []byte(`"password":"bar"`)) { - t.Error("No password found") - } - - fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) - } - - if r.Method == http.MethodGet { - testMethod(t, r, http.MethodGet) - testRequestURL(t, r, "/rest/auth/1/session") - - w.WriteHeader(http.StatusForbidden) - } - }) - - testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") - - _, err := testClient.Authentication.GetCurrentUser(context.Background()) - if err == nil { - t.Errorf("Non nil error expect, received nil") - } -} - -func TestAuthenticationService_GetUserInfo_NonOkStatusCode_Fail(t *testing.T) { - setup() - defer teardown() - - testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - testMethod(t, r, http.MethodPost) - testRequestURL(t, r, "/rest/auth/1/session") - b, err := io.ReadAll(r.Body) - if err != nil { - t.Errorf("Error in read body: %s", err) - } - if !bytes.Contains(b, []byte(`"username":"foo"`)) { - t.Error("No username found") - } - if !bytes.Contains(b, []byte(`"password":"bar"`)) { - t.Error("No password found") - } - - fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) - } - - if r.Method == http.MethodGet { - testMethod(t, r, http.MethodGet) - testRequestURL(t, r, "/rest/auth/1/session") - //any status but 200 - w.WriteHeader(240) - } - }) - - testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") - - _, err := testClient.Authentication.GetCurrentUser(context.Background()) - if err == nil { - t.Errorf("Non nil error expect, received nil") - } -} - -func TestAuthenticationService_GetUserInfo_FailWithoutLogin(t *testing.T) { - // no setup() required here - testClient = new(Client) - - _, err := testClient.Authentication.GetCurrentUser(context.Background()) - if err == nil { - t.Errorf("Expected error, but got %s", err) - } -} - -func TestAuthenticationService_GetUserInfo_Success(t *testing.T) { - setup() - defer teardown() - - testUserInfo := new(Session) - testUserInfo.Name = "foo" - testUserInfo.Self = "https://my.jira.com/rest/api/latest/user?username=foo" - testUserInfo.LoginInfo.FailedLoginCount = 12 - testUserInfo.LoginInfo.LastFailedLoginTime = "2016-09-06T16:41:23.949+0200" - testUserInfo.LoginInfo.LoginCount = 357 - testUserInfo.LoginInfo.PreviousLoginTime = "2016-09-07T11:36:23.476+0200" - - testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - testMethod(t, r, http.MethodPost) - testRequestURL(t, r, "/rest/auth/1/session") - b, err := io.ReadAll(r.Body) - if err != nil { - t.Errorf("Error in read body: %s", err) - } - if !bytes.Contains(b, []byte(`"username":"foo"`)) { - t.Error("No username found") - } - if !bytes.Contains(b, []byte(`"password":"bar"`)) { - t.Error("No password found") - } - - fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) - } - - if r.Method == http.MethodGet { - testMethod(t, r, http.MethodGet) - testRequestURL(t, r, "/rest/auth/1/session") - fmt.Fprint(w, `{"self":"https://my.jira.com/rest/api/latest/user?username=foo","name":"foo","loginInfo":{"failedLoginCount":12,"loginCount":357,"lastFailedLoginTime":"2016-09-06T16:41:23.949+0200","previousLoginTime":"2016-09-07T11:36:23.476+0200"}}`) - } - }) - - testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") - - userinfo, err := testClient.Authentication.GetCurrentUser(context.Background()) - if err != nil { - t.Errorf("Nil error expect, received %s", err) - } - equal := reflect.DeepEqual(*testUserInfo, *userinfo) - - if !equal { - t.Error("The user information doesn't match") - } -} - -func TestAuthenticationService_Logout_Success(t *testing.T) { - setup() - defer teardown() - - testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - testMethod(t, r, http.MethodPost) - testRequestURL(t, r, "/rest/auth/1/session") - b, err := io.ReadAll(r.Body) - if err != nil { - t.Errorf("Error in read body: %s", err) - } - if !bytes.Contains(b, []byte(`"username":"foo"`)) { - t.Error("No username found") - } - if !bytes.Contains(b, []byte(`"password":"bar"`)) { - t.Error("No password found") - } - - fmt.Fprint(w, `{"session":{"name":"JSESSIONID","value":"12345678901234567890"},"loginInfo":{"failedLoginCount":10,"loginCount":127,"lastFailedLoginTime":"2016-03-16T04:22:35.386+0000","previousLoginTime":"2016-03-16T04:22:35.386+0000"}}`) - } - - if r.Method == http.MethodDelete { - // return 204 - w.WriteHeader(http.StatusNoContent) - } - }) - - testClient.Authentication.AcquireSessionCookie(context.Background(), "foo", "bar") - - err := testClient.Authentication.Logout(context.Background()) - if err != nil { - t.Errorf("Expected nil error, got %s", err) - } -} - -func TestAuthenticationService_Logout_FailWithoutLogin(t *testing.T) { - setup() - defer teardown() - - testMux.HandleFunc("/rest/auth/1/session", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodDelete { - // 401 - w.WriteHeader(http.StatusUnauthorized) - } - }) - err := testClient.Authentication.Logout(context.Background()) - if err == nil { - t.Error("Expected not nil, got nil") - } -} diff --git a/cloud/jira.go b/cloud/jira.go index f473a93..e0b9bfb 100644 --- a/cloud/jira.go +++ b/cloud/jira.go @@ -34,15 +34,10 @@ type Client struct { // User agent used when communicating with the Jira API. UserAgent string - // Session storage if the user authenticates with a Session cookie - // TODO Needed in Cloud and/or onpremise? - session *Session - // Reuse a single struct instead of allocating one for each service on the heap. common service // Services used for talking to different parts of the Jira API. - Authentication *AuthenticationService Issue *IssueService Project *ProjectService Board *BoardService @@ -105,8 +100,6 @@ func NewClient(baseURL string, httpClient *http.Client) (*Client, error) { } c.common.client = c - // TODO Check if the authentication service is still needed (because of the transports) - c.Authentication = &AuthenticationService{client: c} c.Issue = (*IssueService)(&c.common) c.Project = (*ProjectService)(&c.common) c.Board = (*BoardService)(&c.common) @@ -153,21 +146,6 @@ func (c *Client) NewRawRequest(ctx context.Context, method, urlStr string, body req.Header.Set("Content-Type", "application/json") - // Set authentication information - if c.Authentication.authType == authTypeSession { - // Set session cookie if there is one - if c.session != nil { - for _, cookie := range c.session.Cookies { - req.AddCookie(cookie) - } - } - } else if c.Authentication.authType == authTypeBasic { - // Set basic auth information - if c.Authentication.username != "" { - req.SetBasicAuth(c.Authentication.username, c.Authentication.password) - } - } - return req, nil } @@ -202,21 +180,6 @@ func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body int req.Header.Set("Content-Type", "application/json") - // Set authentication information - if c.Authentication.authType == authTypeSession { - // Set session cookie if there is one - if c.session != nil { - for _, cookie := range c.session.Cookies { - req.AddCookie(cookie) - } - } - } else if c.Authentication.authType == authTypeBasic { - // Set basic auth information - if c.Authentication.username != "" { - req.SetBasicAuth(c.Authentication.username, c.Authentication.password) - } - } - return req, nil } @@ -263,21 +226,6 @@ func (c *Client) NewMultiPartRequest(ctx context.Context, method, urlStr string, // Set required headers req.Header.Set("X-Atlassian-Token", "nocheck") - // Set authentication information - if c.Authentication.authType == authTypeSession { - // Set session cookie if there is one - if c.session != nil { - for _, cookie := range c.session.Cookies { - req.AddCookie(cookie) - } - } - } else if c.Authentication.authType == authTypeBasic { - // Set basic auth information - if c.Authentication.username != "" { - req.SetBasicAuth(c.Authentication.username, c.Authentication.password) - } - } - return req, nil } diff --git a/cloud/jira_test.go b/cloud/jira_test.go index 9f4869c..61d4b81 100644 --- a/cloud/jira_test.go +++ b/cloud/jira_test.go @@ -107,9 +107,6 @@ func TestNewClient_WithServices(t *testing.T) { if err != nil { t.Errorf("Got an error: %s", err) } - if c.Authentication == nil { - t.Error("No AuthenticationService provided") - } if c.Issue == nil { t.Error("No IssueService provided") } @@ -221,57 +218,6 @@ func TestClient_NewRequest_BadURL(t *testing.T) { testURLParseError(t, err) } -func TestClient_NewRequest_SessionCookies(t *testing.T) { - c, err := NewClient(testJiraInstanceURL, nil) - if err != nil { - t.Errorf("An error occurred. Expected nil. Got %+v.", err) - } - - cookie := &http.Cookie{Name: "testcookie", Value: "testvalue"} - c.session = &Session{Cookies: []*http.Cookie{cookie}} - c.Authentication.authType = authTypeSession - - inURL := "rest/api/2/issue/" - inBody := &Issue{Key: "MESOS"} - req, err := c.NewRequest(context.Background(), http.MethodGet, inURL, inBody) - - if err != nil { - t.Errorf("An error occurred. Expected nil. Got %+v.", err) - } - - if len(req.Cookies()) != len(c.session.Cookies) { - t.Errorf("An error occurred. Expected %d cookie(s). Got %d.", len(c.session.Cookies), len(req.Cookies())) - } - - for i, v := range req.Cookies() { - if v.String() != c.session.Cookies[i].String() { - t.Errorf("An error occurred. Unexpected cookie. Expected %s, actual %s.", v.String(), c.session.Cookies[i].String()) - } - } -} - -func TestClient_NewRequest_BasicAuth(t *testing.T) { - c, err := NewClient(testJiraInstanceURL, nil) - if err != nil { - t.Errorf("An error occurred. Expected nil. Got %+v.", err) - } - - c.Authentication.SetBasicAuth("test-user", "test-password") - - inURL := "rest/api/2/issue/" - inBody := &Issue{Key: "MESOS"} - req, err := c.NewRequest(context.Background(), http.MethodGet, inURL, inBody) - - if err != nil { - t.Errorf("An error occurred. Expected nil. Got %+v.", err) - } - - username, password, ok := req.BasicAuth() - if !ok || username != "test-user" || password != "test-password" { - t.Errorf("An error occurred. Expected basic auth username %s and password %s. Got username %s and password %s.", "test-user", "test-password", username, password) - } -} - // If a nil body is passed to jira.NewRequest, make sure that nil is also passed to http.NewRequest. // In most cases, passing an io.Reader that returns no content is fine, // since there is no difference between an HTTP request body that is an empty string versus one that is not set at all. @@ -296,41 +242,6 @@ func TestClient_NewMultiPartRequest(t *testing.T) { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } - cookie := &http.Cookie{Name: "testcookie", Value: "testvalue"} - c.session = &Session{Cookies: []*http.Cookie{cookie}} - c.Authentication.authType = authTypeSession - - inURL := "rest/api/2/issue/" - inBuf := bytes.NewBufferString("teststring") - req, err := c.NewMultiPartRequest(context.Background(), http.MethodGet, inURL, inBuf) - - if err != nil { - t.Errorf("An error occurred. Expected nil. Got %+v.", err) - } - - if len(req.Cookies()) != len(c.session.Cookies) { - t.Errorf("An error occurred. Expected %d cookie(s). Got %d.", len(c.session.Cookies), len(req.Cookies())) - } - - for i, v := range req.Cookies() { - if v.String() != c.session.Cookies[i].String() { - t.Errorf("An error occurred. Unexpected cookie. Expected %s, actual %s.", v.String(), c.session.Cookies[i].String()) - } - } - - if req.Header.Get("X-Atlassian-Token") != "nocheck" { - t.Errorf("An error occurred. Unexpected X-Atlassian-Token header value. Expected nocheck, actual %s.", req.Header.Get("X-Atlassian-Token")) - } -} - -func TestClient_NewMultiPartRequest_BasicAuth(t *testing.T) { - c, err := NewClient(testJiraInstanceURL, nil) - if err != nil { - t.Errorf("An error occurred. Expected nil. Got %+v.", err) - } - - c.Authentication.SetBasicAuth("test-user", "test-password") - inURL := "rest/api/2/issue/" inBuf := bytes.NewBufferString("teststring") req, err := c.NewMultiPartRequest(context.Background(), http.MethodGet, inURL, inBuf) @@ -339,11 +250,6 @@ func TestClient_NewMultiPartRequest_BasicAuth(t *testing.T) { t.Errorf("An error occurred. Expected nil. Got %+v.", err) } - username, password, ok := req.BasicAuth() - if !ok || username != "test-user" || password != "test-password" { - t.Errorf("An error occurred. Expected basic auth username %s and password %s. Got username %s and password %s.", "test-user", "test-password", username, password) - } - if req.Header.Get("X-Atlassian-Token") != "nocheck" { t.Errorf("An error occurred. Unexpected X-Atlassian-Token header value. Expected nocheck, actual %s.", req.Header.Get("X-Atlassian-Token")) } From e339202b8fe86b0ed0467cecd3a891e8ae172fd7 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 14:22:23 +0200 Subject: [PATCH 122/189] Updated CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 211ddbb..fafcabf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -395,6 +395,7 @@ client, err := jira.NewClient("https://...", tp.Client()) * Cloud/Authentication: Removed `BearerAuthTransport`, because it was a (kind of) duplicate of `BasicAuthTransport` * Cloud/Authentication: Removed `PATAuthTransport`, because it was a (kind of) duplicate of `BasicAuthTransport` * Cloud/Authentication: `BasicAuthTransport.Password` was renamed to `BasicAuthTransport.APIToken` +* Cloud/Authentication: Removes `CookieAuthTransport` and `AuthenticationService`, because this type of auth is not supported by the Jira cloud offering ### Features From 5fccdb3f195d87581912a44f337fd00cb5298c06 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 19:37:04 +0200 Subject: [PATCH 123/189] Cloud: Add TODO comments to methods that need a double check --- cloud/board.go | 18 ++++++++ cloud/component.go | 3 ++ cloud/customer.go | 3 ++ cloud/field.go | 3 ++ cloud/filter.go | 15 +++++++ cloud/group.go | 9 ++++ cloud/issue.go | 95 +++++++++++++++++++++++++++++++++++++++ cloud/issuelinktype.go | 15 +++++++ cloud/metaissue.go | 24 ++++++++++ cloud/organization.go | 33 ++++++++++++++ cloud/permissionscheme.go | 6 +++ cloud/priority.go | 3 ++ cloud/project.go | 9 ++++ cloud/request.go | 6 +++ cloud/resolution.go | 3 ++ cloud/role.go | 6 +++ cloud/servicedesk.go | 18 ++++++++ cloud/sprint.go | 9 ++++ cloud/status.go | 3 ++ cloud/user.go | 21 +++++++++ cloud/version.go | 9 ++++ 21 files changed, 311 insertions(+) diff --git a/cloud/board.go b/cloud/board.go index 992a378..7ae4fd3 100644 --- a/cloud/board.go +++ b/cloud/board.go @@ -128,6 +128,9 @@ type BoardConfigurationColumnStatus struct { // GetAllBoards will returns all boards. This only includes boards that the user has permission to view. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getAllBoards +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *BoardService) GetAllBoards(ctx context.Context, opt *BoardListOptions) (*BoardsList, *Response, error) { apiEndpoint := "rest/agile/1.0/board" url, err := addOptions(apiEndpoint, opt) @@ -153,6 +156,9 @@ func (s *BoardService) GetAllBoards(ctx context.Context, opt *BoardListOptions) // This board will only be returned if the user has permission to view it. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *BoardService) GetBoard(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -178,6 +184,9 @@ func (s *BoardService) GetBoard(ctx context.Context, boardID int) (*Board, *Resp // board will be created instead (remember that board sharing depends on the filter sharing). // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *BoardService) CreateBoard(ctx context.Context, board *Board) (*Board, *Response, error) { apiEndpoint := "rest/agile/1.0/board" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, board) @@ -199,6 +208,9 @@ func (s *BoardService) CreateBoard(ctx context.Context, board *Board) (*Board, * // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-deleteBoard // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *BoardService) DeleteBoard(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) @@ -217,6 +229,9 @@ func (s *BoardService) DeleteBoard(ctx context.Context, boardID int) (*Board, *R // This only includes sprints that the user has permission to view. // // Jira API docs: https://developer.atlassian.com/cloud/jira/software/rest/api-group-board/#api-rest-agile-1-0-board-boardid-sprint-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *BoardService) GetAllSprints(ctx context.Context, boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/sprint", boardID) url, err := addOptions(apiEndpoint, options) @@ -239,6 +254,9 @@ func (s *BoardService) GetAllSprints(ctx context.Context, boardID int, options * // GetBoardConfiguration will return a board configuration for a given board Id // Jira API docs:https://developer.atlassian.com/cloud/jira/software/rest/#api-rest-agile-1-0-board-boardId-configuration-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *BoardService) GetBoardConfiguration(ctx context.Context, boardID int) (*BoardConfiguration, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/configuration", boardID) diff --git a/cloud/component.go b/cloud/component.go index ea97461..bac214c 100644 --- a/cloud/component.go +++ b/cloud/component.go @@ -22,6 +22,9 @@ type CreateComponentOptions struct { } // Create creates a new Jira component based on the given options. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ComponentService) Create(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) { apiEndpoint := "rest/api/2/component" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, options) diff --git a/cloud/customer.go b/cloud/customer.go index d1adc3e..07fecbc 100644 --- a/cloud/customer.go +++ b/cloud/customer.go @@ -39,6 +39,9 @@ type CustomerList struct { // Create creates a ServiceDesk customer. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-customer/#api-rest-servicedeskapi-customer-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (c *CustomerService) Create(ctx context.Context, email, displayName string) (*Customer, *Response, error) { const apiEndpoint = "rest/servicedeskapi/customer" diff --git a/cloud/field.go b/cloud/field.go index 93cb030..f25f49f 100644 --- a/cloud/field.go +++ b/cloud/field.go @@ -35,6 +35,9 @@ type FieldSchema struct { // GetList gets all fields from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *FieldService) GetList(ctx context.Context) ([]Field, *Response, error) { apiEndpoint := "rest/api/2/field" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/cloud/filter.go b/cloud/filter.go index 65035b2..69bfab8 100644 --- a/cloud/filter.go +++ b/cloud/filter.go @@ -120,6 +120,9 @@ type FilterSearchOptions struct { } // GetList retrieves all filters from Jira +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (fs *FilterService) GetList(ctx context.Context) ([]*Filter, *Response, error) { options := &GetQueryOptions{} @@ -145,6 +148,9 @@ func (fs *FilterService) GetList(ctx context.Context) ([]*Filter, *Response, err } // GetFavouriteList retrieves the user's favourited filters from Jira +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (fs *FilterService) GetFavouriteList(ctx context.Context) ([]*Filter, *Response, error) { apiEndpoint := "rest/api/2/filter/favourite" req, err := fs.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -161,6 +167,9 @@ func (fs *FilterService) GetFavouriteList(ctx context.Context) ([]*Filter, *Resp } // Get retrieves a single Filter from Jira +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (fs *FilterService) Get(ctx context.Context, filterID int) (*Filter, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/filter/%d", filterID) req, err := fs.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -180,6 +189,9 @@ func (fs *FilterService) Get(ctx context.Context, filterID int) (*Filter, *Respo // GetMyFilters retrieves the my Filters. // // https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-my-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (fs *FilterService) GetMyFilters(ctx context.Context, opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) { apiEndpoint := "rest/api/3/filter/my" url, err := addOptions(apiEndpoint, opts) @@ -203,6 +215,9 @@ func (fs *FilterService) GetMyFilters(ctx context.Context, opts *GetMyFiltersQue // Search will search for filter according to the search options // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-search-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (fs *FilterService) Search(ctx context.Context, opt *FilterSearchOptions) (*FiltersList, *Response, error) { apiEndpoint := "rest/api/3/filter/search" url, err := addOptions(apiEndpoint, opt) diff --git a/cloud/group.go b/cloud/group.go index 6bd765f..0694b13 100644 --- a/cloud/group.go +++ b/cloud/group.go @@ -65,6 +65,9 @@ type GroupSearchOptions struct { // Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup // // WARNING: This API only returns the first page of group members +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *GroupService) Get(ctx context.Context, name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { var apiEndpoint string if options == nil { @@ -95,6 +98,9 @@ func (s *GroupService) Get(ctx context.Context, name string, options *GroupSearc // Add adds user to group // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-addUserToGroup +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *GroupService) Add(ctx context.Context, groupname string, username string) (*Group, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s", groupname) var user struct { @@ -120,6 +126,9 @@ func (s *GroupService) Add(ctx context.Context, groupname string, username strin // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-removeUserFromGroup // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *GroupService) Remove(ctx context.Context, groupname string, username string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s&username=%s", groupname, username) req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) diff --git a/cloud/issue.go b/cloud/issue.go index e25c522..c0ed078 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -613,6 +613,9 @@ type RemoteLinkStatus struct { // # The given options will be appended to the query string // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getIssue +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) Get(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -642,6 +645,9 @@ func (s *IssueService) Get(ctx context.Context, issueID string, options *GetQuer // The attachment is in the Response.Body of the response. // This is an io.ReadCloser. // Caller must close resp.Body. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) DownloadAttachment(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("secure/attachment/%s/", attachmentID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -659,6 +665,9 @@ func (s *IssueService) DownloadAttachment(ctx context.Context, attachmentID stri } // PostAttachment uploads r (io.Reader) as an attachment to a given issueID +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) PostAttachment(ctx context.Context, issueID string, r io.Reader, attachmentName string) (*[]Attachment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/attachments", issueID) @@ -698,6 +707,9 @@ func (s *IssueService) PostAttachment(ctx context.Context, issueID string, r io. // DeleteAttachment deletes an attachment of a given attachmentID // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) DeleteAttachment(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/attachment/%s", attachmentID) @@ -717,6 +729,9 @@ func (s *IssueService) DeleteAttachment(ctx context.Context, attachmentID string // DeleteLink deletes a link of a given linkID // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) DeleteLink(ctx context.Context, linkID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLink/%s", linkID) @@ -738,6 +753,9 @@ func (s *IssueService) DeleteLink(ctx context.Context, linkID string) (*Response // This method is especially important if you need to read all the worklogs, not just the first page. // // https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/worklog-getIssueWorklog +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) GetWorklogs(ctx context.Context, issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) @@ -760,6 +778,9 @@ func (s *IssueService) GetWorklogs(ctx context.Context, issueID string, options // Applies query options to http request. // This helper is meant to be used with all "QueryOptions" structs. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func WithQueryOptions(options interface{}) func(*http.Request) error { q, err := query.Values(options) if err != nil { @@ -779,6 +800,9 @@ func WithQueryOptions(options interface{}) func(*http.Request) error { // The issueType field must correspond to a sub-task issue type and you must provide a parent field in the issue create request containing the id or key of the parent issue. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-createIssues +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) Create(ctx context.Context, issue *Issue) (*Issue, *Response, error) { apiEndpoint := "rest/api/2/issue" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, issue) @@ -807,6 +831,9 @@ func (s *IssueService) Create(ctx context.Context, issue *Issue) (*Issue, *Respo // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issues/#api-rest-api-2-issue-issueidorkey-put // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) Update(ctx context.Context, issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", issue.Key) url, err := addOptions(apiEndpoint, opts) @@ -833,6 +860,9 @@ func (s *IssueService) Update(ctx context.Context, issue *Issue, opts *UpdateQue // // https://docs.atlassian.com/jira/REST/7.4.0/#api/2/issue-editIssue // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) UpdateIssue(ctx context.Context, jiraID string, data map[string]interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", jiraID) req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, data) @@ -852,6 +882,9 @@ func (s *IssueService) UpdateIssue(ctx context.Context, jiraID string, data map[ // AddComment adds a new comment to issueID. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-addComment +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) AddComment(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment", issueID) req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, comment) @@ -872,6 +905,9 @@ func (s *IssueService) AddComment(ctx context.Context, issueID string, comment * // UpdateComment updates the body of a comment, identified by comment.ID, on the issueID. // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/comment-updateComment +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) UpdateComment(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { reqBody := struct { Body string `json:"body"` @@ -896,6 +932,9 @@ func (s *IssueService) UpdateComment(ctx context.Context, issueID string, commen // DeleteComment Deletes a comment from an issueID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-issue-issueIdOrKey-comment-id-delete +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) DeleteComment(ctx context.Context, issueID, commentID string) error { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, commentID) req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) @@ -916,6 +955,9 @@ func (s *IssueService) DeleteComment(ctx context.Context, issueID, commentID str // AddWorklogRecord adds a new worklog record to issueID. // // https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-worklog-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) AddWorklogRecord(ctx context.Context, issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, record) @@ -943,6 +985,9 @@ func (s *IssueService) AddWorklogRecord(ctx context.Context, issueID string, rec // UpdateWorklogRecord updates a worklog record. // // https://docs.atlassian.com/software/jira/docs/api/REST/7.1.2/#api/2/issue-updateWorklog +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) UpdateWorklogRecord(ctx context.Context, issueID, worklogID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog/%s", issueID, worklogID) req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, record) @@ -971,6 +1016,9 @@ func (s *IssueService) UpdateWorklogRecord(ctx context.Context, issueID, worklog // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issueLink // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) AddLink(ctx context.Context, issueLink *IssueLink) (*Response, error) { apiEndpoint := "rest/api/2/issueLink" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, issueLink) @@ -989,6 +1037,9 @@ func (s *IssueService) AddLink(ctx context.Context, issueLink *IssueLink) (*Resp // Search 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 +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) Search(ctx context.Context, jql string, options *SearchOptions) ([]Issue, *Response, error) { u := url.URL{ Path: "rest/api/2/search", @@ -1034,6 +1085,9 @@ func (s *IssueService) Search(ctx context.Context, jql string, options *SearchOp // SearchPages will get issues from all pages in a search // // Jira API docs: https://developer.atlassian.com/jiradev/jira-apis/jira-rest-apis/jira-rest-api-tutorials/jira-rest-api-example-query-issues +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) SearchPages(ctx context.Context, jql string, options *SearchOptions, f func(Issue) error) error { if options == nil { options = &SearchOptions{ @@ -1076,6 +1130,9 @@ func (s *IssueService) SearchPages(ctx context.Context, jql string, options *Sea } // GetCustomFields returns a map of customfield_* keys with string values +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) GetCustomFields(ctx context.Context, issueID string) (CustomFields, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -1116,6 +1173,9 @@ func (s *IssueService) GetCustomFields(ctx context.Context, issueID string) (Cus // along with fields that are required and their types. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getTransitions +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) GetTransitions(ctx context.Context, id string) ([]Transition, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions?expand=transitions.fields", id) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -1135,6 +1195,9 @@ func (s *IssueService) GetTransitions(ctx context.Context, id string) ([]Transit // When performing the transition you can update or set other issue fields. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) DoTransition(ctx context.Context, ticketID, transitionID string) (*Response, error) { payload := CreateTransitionPayload{ Transition: TransitionPayload{ @@ -1149,6 +1212,9 @@ func (s *IssueService) DoTransition(ctx context.Context, ticketID, transitionID // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) DoTransitionWithPayload(ctx context.Context, ticketID, payload interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions", ticketID) @@ -1177,6 +1243,9 @@ func (s *IssueService) DoTransitionWithPayload(ctx context.Context, ticketID, pa // error if the key is not found. // All values will be packed into Unknowns. This is much convenient. If the struct fields needs to be // configured as well, marshalling and unmarshalling will set the proper fields. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func InitIssueWithMetaAndFields(metaProject *MetaProject, metaIssuetype *MetaIssueType, fieldsConfig map[string]string) (*Issue, error) { issue := new(Issue) issueFields := new(IssueFields) @@ -1248,6 +1317,9 @@ func InitIssueWithMetaAndFields(metaProject *MetaProject, metaIssuetype *MetaIss // Delete will delete a specified issue. // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) Delete(ctx context.Context, issueID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) @@ -1268,6 +1340,9 @@ func (s *IssueService) Delete(ctx context.Context, issueID string) (*Response, e // GetWatchers wil return all the users watching/observing the given issue // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-getIssueWatchers +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) GetWatchers(ctx context.Context, issueID string) (*[]User, *Response, error) { watchesAPIEndpoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) @@ -1301,6 +1376,9 @@ func (s *IssueService) GetWatchers(ctx context.Context, issueID string) (*[]User // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-addWatcher // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) AddWatcher(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) @@ -1321,6 +1399,9 @@ func (s *IssueService) AddWatcher(ctx context.Context, issueID string, userName // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-removeWatcher // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) RemoveWatcher(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) @@ -1341,6 +1422,9 @@ func (s *IssueService) RemoveWatcher(ctx context.Context, issueID string, userNa // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.2/#api/2/issue-assign // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) UpdateAssignee(ctx context.Context, issueID string, assignee *User) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/assignee", issueID) @@ -1357,6 +1441,8 @@ func (s *IssueService) UpdateAssignee(ctx context.Context, issueID string, assig return resp, err } +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (c ChangelogHistory) CreatedTime() (time.Time, error) { var t time.Time // Ignore null @@ -1370,6 +1456,9 @@ func (c ChangelogHistory) CreatedTime() (time.Time, error) { // GetRemoteLinks gets remote issue links on the issue. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getRemoteIssueLinks +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) GetRemoteLinks(ctx context.Context, id string) (*[]RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", id) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -1388,6 +1477,9 @@ func (s *IssueService) GetRemoteLinks(ctx context.Context, id string) (*[]Remote // AddRemoteLink adds a remote link to issueID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-remotelink-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) AddRemoteLink(ctx context.Context, issueID string, remotelink *RemoteLink) (*RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", issueID) req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, remotelink) @@ -1408,6 +1500,9 @@ func (s *IssueService) AddRemoteLink(ctx context.Context, issueID string, remote // UpdateRemoteLink updates a remote issue link by linkID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-remote-links/#api-rest-api-2-issue-issueidorkey-remotelink-linkid-put +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) UpdateRemoteLink(ctx context.Context, issueID string, linkID int, remotelink *RemoteLink) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink/%d", issueID, linkID) req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, remotelink) diff --git a/cloud/issuelinktype.go b/cloud/issuelinktype.go index c228f06..f50e543 100644 --- a/cloud/issuelinktype.go +++ b/cloud/issuelinktype.go @@ -15,6 +15,9 @@ type IssueLinkTypeService service // GetList gets all of the issue link types from Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueLinkTypeService) GetList(ctx context.Context) ([]IssueLinkType, *Response, error) { apiEndpoint := "rest/api/2/issueLinkType" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -33,6 +36,9 @@ func (s *IssueLinkTypeService) GetList(ctx context.Context) ([]IssueLinkType, *R // Get gets info of a specific issue link type from Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueLinkTypeService) Get(ctx context.Context, ID string) (*IssueLinkType, *Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) @@ -51,6 +57,9 @@ func (s *IssueLinkTypeService) Get(ctx context.Context, ID string) (*IssueLinkTy // Create creates an issue link type in Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueLinkTypeService) Create(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := "/rest/api/2/issueLinkType" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, linkType) @@ -77,6 +86,9 @@ func (s *IssueLinkTypeService) Create(ctx context.Context, linkType *IssueLinkTy // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-put // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueLinkTypeService) Update(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", linkType.ID) req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, linkType) @@ -95,6 +107,9 @@ func (s *IssueLinkTypeService) Update(ctx context.Context, linkType *IssueLinkTy // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-delete // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueLinkTypeService) Delete(ctx context.Context, ID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) diff --git a/cloud/metaissue.go b/cloud/metaissue.go index 3e6af1e..0588459 100644 --- a/cloud/metaissue.go +++ b/cloud/metaissue.go @@ -50,6 +50,9 @@ type MetaIssueType struct { } // GetCreateMeta makes the api call to get the meta information without requiring to have a projectKey +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) GetCreateMeta(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { apiEndpoint := "rest/api/2/issue/createmeta" @@ -76,6 +79,9 @@ func (s *IssueService) GetCreateMeta(ctx context.Context, options *GetQueryOptio } // GetEditMeta makes the api call to get the edit meta information for an issue +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) GetEditMeta(ctx context.Context, issue *Issue) (*EditMetaInfo, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/issue/%s/editmeta", issue.Key) @@ -96,6 +102,9 @@ func (s *IssueService) GetEditMeta(ctx context.Context, issue *Issue) (*EditMeta // GetProjectWithName returns a project with "name" from the meta information received. If not found, this returns nil. // The comparison of the name is case insensitive. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject { for _, m := range m.Projects { if strings.EqualFold(m.Name, name) { @@ -107,6 +116,9 @@ func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject { // GetProjectWithKey returns a project with "name" from the meta information received. If not found, this returns nil. // The comparison of the name is case insensitive. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (m *CreateMetaInfo) GetProjectWithKey(key string) *MetaProject { for _, m := range m.Projects { if strings.EqualFold(m.Key, key) { @@ -118,6 +130,9 @@ func (m *CreateMetaInfo) GetProjectWithKey(key string) *MetaProject { // GetIssueTypeWithName returns an IssueType with name from a given MetaProject. If not found, this returns nil. // The comparison of the name is case insensitive +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (p *MetaProject) GetIssueTypeWithName(name string) *MetaIssueType { for _, m := range p.IssueTypes { if strings.EqualFold(m.Name, name) { @@ -146,6 +161,9 @@ func (p *MetaProject) GetIssueTypeWithName(name string) *MetaIssueType { // // the returned map would have "Epic Link" as the key and "customfield_10806" as value. // This choice has been made so that the it is easier to generate the create api request later. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (t *MetaIssueType) GetMandatoryFields() (map[string]string, error) { ret := make(map[string]string) for key := range t.Fields { @@ -166,6 +184,9 @@ func (t *MetaIssueType) GetMandatoryFields() (map[string]string, error) { // GetAllFields returns a map of all the fields for an IssueType. This includes all required and not required. // The key of the returned map is what you see in the form and the value is how it is representated in the jira schema. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (t *MetaIssueType) GetAllFields() (map[string]string, error) { ret := make(map[string]string) for key := range t.Fields { @@ -181,6 +202,9 @@ func (t *MetaIssueType) GetAllFields() (map[string]string, error) { // CheckCompleteAndAvailable checks if the given fields satisfies the mandatory field required to create a issue for the given type // And also if the given fields are available. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (t *MetaIssueType) CheckCompleteAndAvailable(config map[string]string) (bool, error) { mandatory, err := t.GetMandatoryFields() if err != nil { diff --git a/cloud/organization.go b/cloud/organization.go index 70db2f2..11e040d 100644 --- a/cloud/organization.go +++ b/cloud/organization.go @@ -61,6 +61,9 @@ type PropertyKeys struct { // by name. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-group-organization +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) GetAllOrganizations(ctx context.Context, start int, limit int, accountID string) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization?start=%d&limit=%d", start, limit) if accountID != "" { @@ -88,6 +91,9 @@ func (s *OrganizationService) GetAllOrganizations(ctx context.Context, start int // passing the name of the organization. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) CreateOrganization(ctx context.Context, name string) (*Organization, *Response, error) { apiEndPoint := "rest/servicedeskapi/organization" @@ -119,6 +125,9 @@ func (s *OrganizationService) CreateOrganization(ctx context.Context, name strin // other organization details. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) GetOrganization(ctx context.Context, organizationID int) (*Organization, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) @@ -146,6 +155,9 @@ func (s *OrganizationService) GetOrganization(ctx context.Context, organizationI // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-delete // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) DeleteOrganization(ctx context.Context, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) @@ -170,6 +182,9 @@ func (s *OrganizationService) DeleteOrganization(ctx context.Context, organizati // items have been added to an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) GetPropertiesKeys(ctx context.Context, organizationID int) (*PropertyKeys, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property", organizationID) @@ -195,6 +210,9 @@ func (s *OrganizationService) GetPropertiesKeys(ctx context.Context, organizatio // content for an organization's property. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) GetProperty(ctx context.Context, organizationID int, propertyKey string) (*EntityProperty, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) @@ -220,6 +238,9 @@ func (s *OrganizationService) GetProperty(ctx context.Context, organizationID in // resource to store custom data against an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-put +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. // Caller must close resp.Body func (s *OrganizationService) SetProperty(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) @@ -244,6 +265,9 @@ func (s *OrganizationService) SetProperty(ctx context.Context, organizationID in // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-delete // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) DeleteProperty(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) @@ -270,6 +294,9 @@ func (s *OrganizationService) DeleteProperty(ctx context.Context, organizationID // a user is associated with an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) GetUsers(ctx context.Context, organizationID int, start int, limit int) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user?start=%d&limit=%d", organizationID, start, limit) @@ -294,6 +321,9 @@ func (s *OrganizationService) GetUsers(ctx context.Context, organizationID int, // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-post // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) AddUsers(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) @@ -316,6 +346,9 @@ func (s *OrganizationService) AddUsers(ctx context.Context, organizationID int, // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-delete // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) RemoveUsers(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) diff --git a/cloud/permissionscheme.go b/cloud/permissionscheme.go index 51a1ee1..c8c3f31 100644 --- a/cloud/permissionscheme.go +++ b/cloud/permissionscheme.go @@ -31,6 +31,9 @@ type Holder struct { // GetList returns a list of all permission schemes // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *PermissionSchemeService) GetList(ctx context.Context) (*PermissionSchemes, *Response, error) { apiEndpoint := "/rest/api/3/permissionscheme" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -51,6 +54,9 @@ func (s *PermissionSchemeService) GetList(ctx context.Context) (*PermissionSchem // Get returns a full representation of the permission scheme for the schemeID // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-schemeId-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *PermissionSchemeService) Get(ctx context.Context, schemeID int) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/3/permissionscheme/%d", schemeID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/cloud/priority.go b/cloud/priority.go index b89e17f..fe24a41 100644 --- a/cloud/priority.go +++ b/cloud/priority.go @@ -24,6 +24,9 @@ type Priority struct { // GetList gets all priorities from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-priority-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *PriorityService) GetList(ctx context.Context) ([]Priority, *Response, error) { apiEndpoint := "rest/api/2/priority" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/cloud/project.go b/cloud/project.go index 3fa6d49..add12bf 100644 --- a/cloud/project.go +++ b/cloud/project.go @@ -84,6 +84,9 @@ type PermissionScheme struct { // a list of all projects and their supported issuetypes. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-projects/#api-rest-api-2-project-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ProjectService) GetAll(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { apiEndpoint := "rest/api/2/project" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -114,6 +117,9 @@ func (s *ProjectService) GetAll(ctx context.Context, options *GetQueryOptions) ( // This can be an project id, or an project key. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ProjectService) Get(ctx context.Context, projectID string) (*Project, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/project/%s", projectID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -136,6 +142,9 @@ func (s *ProjectService) Get(ctx context.Context, projectID string) (*Project, * // This can be an project id, or an project key. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ProjectService) GetPermissionScheme(ctx context.Context, projectID string) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s/permissionscheme", projectID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/cloud/request.go b/cloud/request.go index c6a1e80..30f8d03 100644 --- a/cloud/request.go +++ b/cloud/request.go @@ -58,6 +58,9 @@ type RequestComment struct { // Create creates a new request. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (r *RequestService) Create(ctx context.Context, requester string, participants []string, request *Request) (*Request, *Response, error) { apiEndpoint := "rest/servicedeskapi/request" @@ -94,6 +97,9 @@ func (r *RequestService) Create(ctx context.Context, requester string, participa // CreateComment creates a comment on a request. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-comment-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (r *RequestService) CreateComment(ctx context.Context, issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/request/%v/comment", issueIDOrKey) diff --git a/cloud/resolution.go b/cloud/resolution.go index d08fb59..57327a7 100644 --- a/cloud/resolution.go +++ b/cloud/resolution.go @@ -22,6 +22,9 @@ type Resolution struct { // GetList gets all resolutions from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-resolution-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ResolutionService) GetList(ctx context.Context) ([]Resolution, *Response, error) { apiEndpoint := "rest/api/2/resolution" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/cloud/role.go b/cloud/role.go index 4ab9327..3fd18d8 100644 --- a/cloud/role.go +++ b/cloud/role.go @@ -38,6 +38,9 @@ type ActorUser struct { // GetList returns a list of all available project roles // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *RoleService) GetList(ctx context.Context) (*[]Role, *Response, error) { apiEndpoint := "rest/api/3/role" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -56,6 +59,9 @@ func (s *RoleService) GetList(ctx context.Context) (*[]Role, *Response, error) { // Get retreives a single Role from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-id-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *RoleService) Get(ctx context.Context, roleID int) (*Role, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/3/role/%d", roleID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/cloud/servicedesk.go b/cloud/servicedesk.go index c370c20..dac54f7 100644 --- a/cloud/servicedesk.go +++ b/cloud/servicedesk.go @@ -22,6 +22,9 @@ type ServiceDeskOrganizationDTO struct { // all organizations associated with a service desk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ServiceDeskService) GetOrganizations(ctx context.Context, serviceDeskID interface{}, start int, limit int, accountID string) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization?start=%d&limit=%d", serviceDeskID, start, limit) if accountID != "" { @@ -52,6 +55,9 @@ func (s *ServiceDeskService) GetOrganizations(ctx context.Context, serviceDeskID // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-post // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ServiceDeskService) AddOrganization(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization", serviceDeskID) @@ -81,6 +87,9 @@ func (s *ServiceDeskService) AddOrganization(ctx context.Context, serviceDeskID // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-delete // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ServiceDeskService) RemoveOrganization(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization", serviceDeskID) @@ -106,6 +115,9 @@ func (s *ServiceDeskService) RemoveOrganization(ctx context.Context, serviceDesk // AddCustomers adds customers to the given service desk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ServiceDeskService) AddCustomers(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) @@ -133,6 +145,9 @@ func (s *ServiceDeskService) AddCustomers(ctx context.Context, serviceDeskID int // RemoveCustomers removes customers to the given service desk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-delete +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ServiceDeskService) RemoveCustomers(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) @@ -160,6 +175,9 @@ func (s *ServiceDeskService) RemoveCustomers(ctx context.Context, serviceDeskID // ListCustomers lists customers for a ServiceDesk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ServiceDeskService) ListCustomers(ctx context.Context, serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/cloud/sprint.go b/cloud/sprint.go index 407fe40..ca4f83a 100644 --- a/cloud/sprint.go +++ b/cloud/sprint.go @@ -28,6 +28,9 @@ type IssuesInSprintResult struct { // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-moveIssuesToSprint // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *SprintService) MoveIssuesToSprint(ctx context.Context, sprintID int, issueIDs []string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) @@ -51,6 +54,9 @@ func (s *SprintService) MoveIssuesToSprint(ctx context.Context, sprintID int, is // By default, the returned issues are ordered by rank. // // Jira API Docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-getIssuesForSprint +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *SprintService) GetIssuesForSprint(ctx context.Context, sprintID int) ([]Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) @@ -79,6 +85,9 @@ func (s *SprintService) GetIssuesForSprint(ctx context.Context, sprintID int) ([ // Jira API docs: https://docs.atlassian.com/jira-software/REST/7.3.1/#agile/1.0/issue-getIssue // // TODO: create agile service for holding all agile apis' implementation +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *SprintService) GetIssue(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/issue/%s", issueID) diff --git a/cloud/status.go b/cloud/status.go index fe79666..910cb36 100644 --- a/cloud/status.go +++ b/cloud/status.go @@ -25,6 +25,9 @@ type Status struct { // GetAllStatuses returns a list of all statuses associated with workflows. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-status-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *StatusService) GetAllStatuses(ctx context.Context) ([]Status, *Response, error) { apiEndpoint := "rest/api/2/status" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/cloud/user.go b/cloud/user.go index 5bce9e3..8fa8536 100644 --- a/cloud/user.go +++ b/cloud/user.go @@ -47,6 +47,9 @@ type userSearchF func(userSearch) userSearch // Get gets user info from Jira using its Account Id // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *UserService) Get(ctx context.Context, accountId string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -66,6 +69,9 @@ func (s *UserService) Get(ctx context.Context, accountId string) (*User, *Respon // Searching by another parameter that is not accountId is deprecated, // but this method is kept for backwards compatibility // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *UserService) GetByAccountID(ctx context.Context, accountID string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -84,6 +90,9 @@ func (s *UserService) GetByAccountID(ctx context.Context, accountID string) (*Us // Create creates an user in Jira. // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-createUser +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *UserService) Create(ctx context.Context, user *User) (*User, *Response, error) { apiEndpoint := "/rest/api/2/user" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, user) @@ -111,6 +120,9 @@ func (s *UserService) Create(ctx context.Context, user *User) (*User, *Response, // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-delete // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *UserService) Delete(ctx context.Context, accountId string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) @@ -128,6 +140,9 @@ func (s *UserService) Delete(ctx context.Context, accountId string) (*Response, // GetGroups returns the groups which the user belongs to // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-groups-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *UserService) GetGroups(ctx context.Context, accountId string) (*[]UserGroup, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?accountId=%s", accountId) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -146,6 +161,9 @@ func (s *UserService) GetGroups(ctx context.Context, accountId string) (*[]UserG // GetSelf information about the current logged-in user // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-myself-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *UserService) GetSelf(ctx context.Context) (*User, *Response, error) { const apiEndpoint = "rest/api/2/myself" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -220,6 +238,9 @@ func WithProperty(property string) userSearchF { // It can find users by email or display name using the query parameter // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-search-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *UserService) Find(ctx context.Context, property string, tweaks ...userSearchF) ([]User, *Response, error) { search := []userSearchParam{ { diff --git a/cloud/version.go b/cloud/version.go index 4c1e1af..419d0d5 100644 --- a/cloud/version.go +++ b/cloud/version.go @@ -29,6 +29,9 @@ type Version struct { // Get gets version info from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *VersionService) Get(ctx context.Context, versionID int) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/version/%v", versionID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -47,6 +50,9 @@ func (s *VersionService) Get(ctx context.Context, versionID int) (*Version, *Res // Create creates a version in Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *VersionService) Create(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := "/rest/api/2/version" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, version) @@ -73,6 +79,9 @@ func (s *VersionService) Create(ctx context.Context, version *Version) (*Version // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-put // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *VersionService) Update(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/version/%v", version.ID) req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, version) From 6c76facbbcde9436e148a660c1d5167cd5c92905 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 19:37:29 +0200 Subject: [PATCH 124/189] On Premise: Add TODO comments to methods that need a double check --- onpremise/board.go | 18 +++++++ onpremise/component.go | 3 ++ onpremise/customer.go | 3 ++ onpremise/field.go | 3 ++ onpremise/filter.go | 15 ++++++ onpremise/group.go | 9 ++++ onpremise/issue.go | 95 +++++++++++++++++++++++++++++++++++ onpremise/issuelinktype.go | 15 ++++++ onpremise/metaissue.go | 24 +++++++++ onpremise/organization.go | 33 ++++++++++++ onpremise/permissionscheme.go | 6 +++ onpremise/priority.go | 3 ++ onpremise/project.go | 9 ++++ onpremise/request.go | 6 +++ onpremise/resolution.go | 3 ++ onpremise/role.go | 6 +++ onpremise/servicedesk.go | 18 +++++++ onpremise/sprint.go | 9 ++++ onpremise/status.go | 3 ++ onpremise/user.go | 42 ++++++++++++++++ onpremise/version.go | 9 ++++ 21 files changed, 332 insertions(+) diff --git a/onpremise/board.go b/onpremise/board.go index 8b92be7..3fdd1f2 100644 --- a/onpremise/board.go +++ b/onpremise/board.go @@ -128,6 +128,9 @@ type BoardConfigurationColumnStatus struct { // GetAllBoards will returns all boards. This only includes boards that the user has permission to view. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getAllBoards +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *BoardService) GetAllBoards(ctx context.Context, opt *BoardListOptions) (*BoardsList, *Response, error) { apiEndpoint := "rest/agile/1.0/board" url, err := addOptions(apiEndpoint, opt) @@ -153,6 +156,9 @@ func (s *BoardService) GetAllBoards(ctx context.Context, opt *BoardListOptions) // This board will only be returned if the user has permission to view it. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *BoardService) GetBoard(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -178,6 +184,9 @@ func (s *BoardService) GetBoard(ctx context.Context, boardID int) (*Board, *Resp // board will be created instead (remember that board sharing depends on the filter sharing). // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *BoardService) CreateBoard(ctx context.Context, board *Board) (*Board, *Response, error) { apiEndpoint := "rest/agile/1.0/board" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, board) @@ -199,6 +208,9 @@ func (s *BoardService) CreateBoard(ctx context.Context, board *Board) (*Board, * // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-deleteBoard // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *BoardService) DeleteBoard(ctx context.Context, boardID int) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) @@ -217,6 +229,9 @@ func (s *BoardService) DeleteBoard(ctx context.Context, boardID int) (*Board, *R // This only includes sprints that the user has permission to view. // // Jira API docs: https://developer.atlassian.com/cloud/jira/software/rest/api-group-board/#api-rest-agile-1-0-board-boardid-sprint-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *BoardService) GetAllSprints(ctx context.Context, boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/sprint", boardID) url, err := addOptions(apiEndpoint, options) @@ -239,6 +254,9 @@ func (s *BoardService) GetAllSprints(ctx context.Context, boardID int, options * // GetBoardConfiguration will return a board configuration for a given board Id // Jira API docs:https://developer.atlassian.com/cloud/jira/software/rest/#api-rest-agile-1-0-board-boardId-configuration-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *BoardService) GetBoardConfiguration(ctx context.Context, boardID int) (*BoardConfiguration, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/configuration", boardID) diff --git a/onpremise/component.go b/onpremise/component.go index a1b841b..14ca450 100644 --- a/onpremise/component.go +++ b/onpremise/component.go @@ -22,6 +22,9 @@ type CreateComponentOptions struct { } // Create creates a new Jira component based on the given options. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ComponentService) Create(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) { apiEndpoint := "rest/api/2/component" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, options) diff --git a/onpremise/customer.go b/onpremise/customer.go index b7c11c9..03732ee 100644 --- a/onpremise/customer.go +++ b/onpremise/customer.go @@ -39,6 +39,9 @@ type CustomerList struct { // Create creates a ServiceDesk customer. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-customer/#api-rest-servicedeskapi-customer-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (c *CustomerService) Create(ctx context.Context, email, displayName string) (*Customer, *Response, error) { const apiEndpoint = "rest/servicedeskapi/customer" diff --git a/onpremise/field.go b/onpremise/field.go index 2bc2489..ec548da 100644 --- a/onpremise/field.go +++ b/onpremise/field.go @@ -35,6 +35,9 @@ type FieldSchema struct { // GetList gets all fields from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *FieldService) GetList(ctx context.Context) ([]Field, *Response, error) { apiEndpoint := "rest/api/2/field" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/onpremise/filter.go b/onpremise/filter.go index d861f0a..8876f26 100644 --- a/onpremise/filter.go +++ b/onpremise/filter.go @@ -120,6 +120,9 @@ type FilterSearchOptions struct { } // GetList retrieves all filters from Jira +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (fs *FilterService) GetList(ctx context.Context) ([]*Filter, *Response, error) { options := &GetQueryOptions{} @@ -145,6 +148,9 @@ func (fs *FilterService) GetList(ctx context.Context) ([]*Filter, *Response, err } // GetFavouriteList retrieves the user's favourited filters from Jira +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (fs *FilterService) GetFavouriteList(ctx context.Context) ([]*Filter, *Response, error) { apiEndpoint := "rest/api/2/filter/favourite" req, err := fs.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -161,6 +167,9 @@ func (fs *FilterService) GetFavouriteList(ctx context.Context) ([]*Filter, *Resp } // Get retrieves a single Filter from Jira +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (fs *FilterService) Get(ctx context.Context, filterID int) (*Filter, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/filter/%d", filterID) req, err := fs.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -180,6 +189,9 @@ func (fs *FilterService) Get(ctx context.Context, filterID int) (*Filter, *Respo // GetMyFilters retrieves the my Filters. // // https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-my-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (fs *FilterService) GetMyFilters(ctx context.Context, opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) { apiEndpoint := "rest/api/3/filter/my" url, err := addOptions(apiEndpoint, opts) @@ -203,6 +215,9 @@ func (fs *FilterService) GetMyFilters(ctx context.Context, opts *GetMyFiltersQue // Search will search for filter according to the search options // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-search-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (fs *FilterService) Search(ctx context.Context, opt *FilterSearchOptions) (*FiltersList, *Response, error) { apiEndpoint := "rest/api/3/filter/search" url, err := addOptions(apiEndpoint, opt) diff --git a/onpremise/group.go b/onpremise/group.go index 07a3694..82a8c95 100644 --- a/onpremise/group.go +++ b/onpremise/group.go @@ -65,6 +65,9 @@ type GroupSearchOptions struct { // Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup // // WARNING: This API only returns the first page of group members +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *GroupService) Get(ctx context.Context, name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { var apiEndpoint string if options == nil { @@ -95,6 +98,9 @@ func (s *GroupService) Get(ctx context.Context, name string, options *GroupSearc // Add adds user to group // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-addUserToGroup +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *GroupService) Add(ctx context.Context, groupname string, username string) (*Group, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s", groupname) var user struct { @@ -120,6 +126,9 @@ func (s *GroupService) Add(ctx context.Context, groupname string, username strin // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-removeUserFromGroup // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *GroupService) Remove(ctx context.Context, groupname string, username string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s&username=%s", groupname, username) req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) diff --git a/onpremise/issue.go b/onpremise/issue.go index a8828be..c83402a 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -613,6 +613,9 @@ type RemoteLinkStatus struct { // # The given options will be appended to the query string // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getIssue +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) Get(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -642,6 +645,9 @@ func (s *IssueService) Get(ctx context.Context, issueID string, options *GetQuer // The attachment is in the Response.Body of the response. // This is an io.ReadCloser. // Caller must close resp.Body. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) DownloadAttachment(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("secure/attachment/%s/", attachmentID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -659,6 +665,9 @@ func (s *IssueService) DownloadAttachment(ctx context.Context, attachmentID stri } // PostAttachment uploads r (io.Reader) as an attachment to a given issueID +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) PostAttachment(ctx context.Context, issueID string, r io.Reader, attachmentName string) (*[]Attachment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/attachments", issueID) @@ -698,6 +707,9 @@ func (s *IssueService) PostAttachment(ctx context.Context, issueID string, r io. // DeleteAttachment deletes an attachment of a given attachmentID // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) DeleteAttachment(ctx context.Context, attachmentID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/attachment/%s", attachmentID) @@ -717,6 +729,9 @@ func (s *IssueService) DeleteAttachment(ctx context.Context, attachmentID string // DeleteLink deletes a link of a given linkID // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) DeleteLink(ctx context.Context, linkID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLink/%s", linkID) @@ -738,6 +753,9 @@ func (s *IssueService) DeleteLink(ctx context.Context, linkID string) (*Response // This method is especially important if you need to read all the worklogs, not just the first page. // // https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/worklog-getIssueWorklog +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) GetWorklogs(ctx context.Context, issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) @@ -760,6 +778,9 @@ func (s *IssueService) GetWorklogs(ctx context.Context, issueID string, options // Applies query options to http request. // This helper is meant to be used with all "QueryOptions" structs. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func WithQueryOptions(options interface{}) func(*http.Request) error { q, err := query.Values(options) if err != nil { @@ -779,6 +800,9 @@ func WithQueryOptions(options interface{}) func(*http.Request) error { // The issueType field must correspond to a sub-task issue type and you must provide a parent field in the issue create request containing the id or key of the parent issue. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-createIssues +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) Create(ctx context.Context, issue *Issue) (*Issue, *Response, error) { apiEndpoint := "rest/api/2/issue" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, issue) @@ -807,6 +831,9 @@ func (s *IssueService) Create(ctx context.Context, issue *Issue) (*Issue, *Respo // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issues/#api-rest-api-2-issue-issueidorkey-put // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) Update(ctx context.Context, issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", issue.Key) url, err := addOptions(apiEndpoint, opts) @@ -833,6 +860,9 @@ func (s *IssueService) Update(ctx context.Context, issue *Issue, opts *UpdateQue // // https://docs.atlassian.com/jira/REST/7.4.0/#api/2/issue-editIssue // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) UpdateIssue(ctx context.Context, jiraID string, data map[string]interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", jiraID) req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, data) @@ -852,6 +882,9 @@ func (s *IssueService) UpdateIssue(ctx context.Context, jiraID string, data map[ // AddComment adds a new comment to issueID. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-addComment +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) AddComment(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment", issueID) req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, comment) @@ -872,6 +905,9 @@ func (s *IssueService) AddComment(ctx context.Context, issueID string, comment * // UpdateComment updates the body of a comment, identified by comment.ID, on the issueID. // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/comment-updateComment +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) UpdateComment(ctx context.Context, issueID string, comment *Comment) (*Comment, *Response, error) { reqBody := struct { Body string `json:"body"` @@ -896,6 +932,9 @@ func (s *IssueService) UpdateComment(ctx context.Context, issueID string, commen // DeleteComment Deletes a comment from an issueID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-issue-issueIdOrKey-comment-id-delete +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) DeleteComment(ctx context.Context, issueID, commentID string) error { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, commentID) req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) @@ -916,6 +955,9 @@ func (s *IssueService) DeleteComment(ctx context.Context, issueID, commentID str // AddWorklogRecord adds a new worklog record to issueID. // // https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-worklog-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) AddWorklogRecord(ctx context.Context, issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID) req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, record) @@ -943,6 +985,9 @@ func (s *IssueService) AddWorklogRecord(ctx context.Context, issueID string, rec // UpdateWorklogRecord updates a worklog record. // // https://docs.atlassian.com/software/jira/docs/api/REST/7.1.2/#api/2/issue-updateWorklog +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) UpdateWorklogRecord(ctx context.Context, issueID, worklogID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog/%s", issueID, worklogID) req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, record) @@ -971,6 +1016,9 @@ func (s *IssueService) UpdateWorklogRecord(ctx context.Context, issueID, worklog // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issueLink // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) AddLink(ctx context.Context, issueLink *IssueLink) (*Response, error) { apiEndpoint := "rest/api/2/issueLink" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, issueLink) @@ -989,6 +1037,9 @@ func (s *IssueService) AddLink(ctx context.Context, issueLink *IssueLink) (*Resp // Search 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 +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) Search(ctx context.Context, jql string, options *SearchOptions) ([]Issue, *Response, error) { u := url.URL{ Path: "rest/api/2/search", @@ -1034,6 +1085,9 @@ func (s *IssueService) Search(ctx context.Context, jql string, options *SearchOp // SearchPages will get issues from all pages in a search // // Jira API docs: https://developer.atlassian.com/jiradev/jira-apis/jira-rest-apis/jira-rest-api-tutorials/jira-rest-api-example-query-issues +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) SearchPages(ctx context.Context, jql string, options *SearchOptions, f func(Issue) error) error { if options == nil { options = &SearchOptions{ @@ -1076,6 +1130,9 @@ func (s *IssueService) SearchPages(ctx context.Context, jql string, options *Sea } // GetCustomFields returns a map of customfield_* keys with string values +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) GetCustomFields(ctx context.Context, issueID string) (CustomFields, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -1116,6 +1173,9 @@ func (s *IssueService) GetCustomFields(ctx context.Context, issueID string) (Cus // along with fields that are required and their types. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getTransitions +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) GetTransitions(ctx context.Context, id string) ([]Transition, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions?expand=transitions.fields", id) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -1135,6 +1195,9 @@ func (s *IssueService) GetTransitions(ctx context.Context, id string) ([]Transit // When performing the transition you can update or set other issue fields. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) DoTransition(ctx context.Context, ticketID, transitionID string) (*Response, error) { payload := CreateTransitionPayload{ Transition: TransitionPayload{ @@ -1149,6 +1212,9 @@ func (s *IssueService) DoTransition(ctx context.Context, ticketID, transitionID // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) DoTransitionWithPayload(ctx context.Context, ticketID, payload interface{}) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions", ticketID) @@ -1177,6 +1243,9 @@ func (s *IssueService) DoTransitionWithPayload(ctx context.Context, ticketID, pa // error if the key is not found. // All values will be packed into Unknowns. This is much convenient. If the struct fields needs to be // configured as well, marshalling and unmarshalling will set the proper fields. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func InitIssueWithMetaAndFields(metaProject *MetaProject, metaIssuetype *MetaIssueType, fieldsConfig map[string]string) (*Issue, error) { issue := new(Issue) issueFields := new(IssueFields) @@ -1248,6 +1317,9 @@ func InitIssueWithMetaAndFields(metaProject *MetaProject, metaIssuetype *MetaIss // Delete will delete a specified issue. // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) Delete(ctx context.Context, issueID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID) @@ -1268,6 +1340,9 @@ func (s *IssueService) Delete(ctx context.Context, issueID string) (*Response, e // GetWatchers wil return all the users watching/observing the given issue // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-getIssueWatchers +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) GetWatchers(ctx context.Context, issueID string) (*[]User, *Response, error) { watchesAPIEndpoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) @@ -1301,6 +1376,9 @@ func (s *IssueService) GetWatchers(ctx context.Context, issueID string) (*[]User // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-addWatcher // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) AddWatcher(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) @@ -1321,6 +1399,9 @@ func (s *IssueService) AddWatcher(ctx context.Context, issueID string, userName // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-removeWatcher // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) RemoveWatcher(ctx context.Context, issueID string, userName string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/watchers", issueID) @@ -1341,6 +1422,9 @@ func (s *IssueService) RemoveWatcher(ctx context.Context, issueID string, userNa // // Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.2/#api/2/issue-assign // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) UpdateAssignee(ctx context.Context, issueID string, assignee *User) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/assignee", issueID) @@ -1357,6 +1441,8 @@ func (s *IssueService) UpdateAssignee(ctx context.Context, issueID string, assig return resp, err } +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (c ChangelogHistory) CreatedTime() (time.Time, error) { var t time.Time // Ignore null @@ -1370,6 +1456,9 @@ func (c ChangelogHistory) CreatedTime() (time.Time, error) { // GetRemoteLinks gets remote issue links on the issue. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getRemoteIssueLinks +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) GetRemoteLinks(ctx context.Context, id string) (*[]RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", id) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -1388,6 +1477,9 @@ func (s *IssueService) GetRemoteLinks(ctx context.Context, id string) (*[]Remote // AddRemoteLink adds a remote link to issueID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-remotelink-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) AddRemoteLink(ctx context.Context, issueID string, remotelink *RemoteLink) (*RemoteLink, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", issueID) req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, remotelink) @@ -1408,6 +1500,9 @@ func (s *IssueService) AddRemoteLink(ctx context.Context, issueID string, remote // UpdateRemoteLink updates a remote issue link by linkID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-remote-links/#api-rest-api-2-issue-issueidorkey-remotelink-linkid-put +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) UpdateRemoteLink(ctx context.Context, issueID string, linkID int, remotelink *RemoteLink) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink/%d", issueID, linkID) req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, remotelink) diff --git a/onpremise/issuelinktype.go b/onpremise/issuelinktype.go index 2177363..bf4687d 100644 --- a/onpremise/issuelinktype.go +++ b/onpremise/issuelinktype.go @@ -15,6 +15,9 @@ type IssueLinkTypeService service // GetList gets all of the issue link types from Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueLinkTypeService) GetList(ctx context.Context) ([]IssueLinkType, *Response, error) { apiEndpoint := "rest/api/2/issueLinkType" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -33,6 +36,9 @@ func (s *IssueLinkTypeService) GetList(ctx context.Context) ([]IssueLinkType, *R // Get gets info of a specific issue link type from Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueLinkTypeService) Get(ctx context.Context, ID string) (*IssueLinkType, *Response, error) { apiEndPoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndPoint, nil) @@ -51,6 +57,9 @@ func (s *IssueLinkTypeService) Get(ctx context.Context, ID string) (*IssueLinkTy // Create creates an issue link type in Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueLinkTypeService) Create(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := "/rest/api/2/issueLinkType" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, linkType) @@ -77,6 +86,9 @@ func (s *IssueLinkTypeService) Create(ctx context.Context, linkType *IssueLinkTy // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-put // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueLinkTypeService) Update(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", linkType.ID) req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, linkType) @@ -95,6 +107,9 @@ func (s *IssueLinkTypeService) Update(ctx context.Context, linkType *IssueLinkTy // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-delete // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueLinkTypeService) Delete(ctx context.Context, ID string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) diff --git a/onpremise/metaissue.go b/onpremise/metaissue.go index a1b104c..006ea0a 100644 --- a/onpremise/metaissue.go +++ b/onpremise/metaissue.go @@ -50,6 +50,9 @@ type MetaIssueType struct { } // GetCreateMeta makes the api call to get the meta information without requiring to have a projectKey +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) GetCreateMeta(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { apiEndpoint := "rest/api/2/issue/createmeta" @@ -76,6 +79,9 @@ func (s *IssueService) GetCreateMeta(ctx context.Context, options *GetQueryOptio } // GetEditMeta makes the api call to get the edit meta information for an issue +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) GetEditMeta(ctx context.Context, issue *Issue) (*EditMetaInfo, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/issue/%s/editmeta", issue.Key) @@ -96,6 +102,9 @@ func (s *IssueService) GetEditMeta(ctx context.Context, issue *Issue) (*EditMeta // GetProjectWithName returns a project with "name" from the meta information received. If not found, this returns nil. // The comparison of the name is case insensitive. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject { for _, m := range m.Projects { if strings.EqualFold(m.Name, name) { @@ -107,6 +116,9 @@ func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject { // GetProjectWithKey returns a project with "name" from the meta information received. If not found, this returns nil. // The comparison of the name is case insensitive. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (m *CreateMetaInfo) GetProjectWithKey(key string) *MetaProject { for _, m := range m.Projects { if strings.EqualFold(m.Key, key) { @@ -118,6 +130,9 @@ func (m *CreateMetaInfo) GetProjectWithKey(key string) *MetaProject { // GetIssueTypeWithName returns an IssueType with name from a given MetaProject. If not found, this returns nil. // The comparison of the name is case insensitive +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (p *MetaProject) GetIssueTypeWithName(name string) *MetaIssueType { for _, m := range p.IssueTypes { if strings.EqualFold(m.Name, name) { @@ -146,6 +161,9 @@ func (p *MetaProject) GetIssueTypeWithName(name string) *MetaIssueType { // // the returned map would have "Epic Link" as the key and "customfield_10806" as value. // This choice has been made so that the it is easier to generate the create api request later. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (t *MetaIssueType) GetMandatoryFields() (map[string]string, error) { ret := make(map[string]string) for key := range t.Fields { @@ -166,6 +184,9 @@ func (t *MetaIssueType) GetMandatoryFields() (map[string]string, error) { // GetAllFields returns a map of all the fields for an IssueType. This includes all required and not required. // The key of the returned map is what you see in the form and the value is how it is representated in the jira schema. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (t *MetaIssueType) GetAllFields() (map[string]string, error) { ret := make(map[string]string) for key := range t.Fields { @@ -181,6 +202,9 @@ func (t *MetaIssueType) GetAllFields() (map[string]string, error) { // CheckCompleteAndAvailable checks if the given fields satisfies the mandatory field required to create a issue for the given type // And also if the given fields are available. +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (t *MetaIssueType) CheckCompleteAndAvailable(config map[string]string) (bool, error) { mandatory, err := t.GetMandatoryFields() if err != nil { diff --git a/onpremise/organization.go b/onpremise/organization.go index c75ba9d..aae4d2a 100644 --- a/onpremise/organization.go +++ b/onpremise/organization.go @@ -61,6 +61,9 @@ type PropertyKeys struct { // by name. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-group-organization +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) GetAllOrganizations(ctx context.Context, start int, limit int, accountID string) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization?start=%d&limit=%d", start, limit) if accountID != "" { @@ -88,6 +91,9 @@ func (s *OrganizationService) GetAllOrganizations(ctx context.Context, start int // passing the name of the organization. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) CreateOrganization(ctx context.Context, name string) (*Organization, *Response, error) { apiEndPoint := "rest/servicedeskapi/organization" @@ -119,6 +125,9 @@ func (s *OrganizationService) CreateOrganization(ctx context.Context, name strin // other organization details. // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) GetOrganization(ctx context.Context, organizationID int) (*Organization, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) @@ -146,6 +155,9 @@ func (s *OrganizationService) GetOrganization(ctx context.Context, organizationI // // Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-delete // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) DeleteOrganization(ctx context.Context, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID) @@ -170,6 +182,9 @@ func (s *OrganizationService) DeleteOrganization(ctx context.Context, organizati // items have been added to an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) GetPropertiesKeys(ctx context.Context, organizationID int) (*PropertyKeys, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property", organizationID) @@ -195,6 +210,9 @@ func (s *OrganizationService) GetPropertiesKeys(ctx context.Context, organizatio // content for an organization's property. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) GetProperty(ctx context.Context, organizationID int, propertyKey string) (*EntityProperty, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) @@ -221,6 +239,9 @@ func (s *OrganizationService) GetProperty(ctx context.Context, organizationID in // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-put // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) SetProperty(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) @@ -244,6 +265,9 @@ func (s *OrganizationService) SetProperty(ctx context.Context, organizationID in // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-delete // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) DeleteProperty(ctx context.Context, organizationID int, propertyKey string) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey) @@ -270,6 +294,9 @@ func (s *OrganizationService) DeleteProperty(ctx context.Context, organizationID // a user is associated with an organization. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) GetUsers(ctx context.Context, organizationID int, start int, limit int) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user?start=%d&limit=%d", organizationID, start, limit) @@ -294,6 +321,9 @@ func (s *OrganizationService) GetUsers(ctx context.Context, organizationID int, // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-post // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) AddUsers(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) @@ -316,6 +346,9 @@ func (s *OrganizationService) AddUsers(ctx context.Context, organizationID int, // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-delete // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *OrganizationService) RemoveUsers(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID) diff --git a/onpremise/permissionscheme.go b/onpremise/permissionscheme.go index 0f5f29d..905ed5f 100644 --- a/onpremise/permissionscheme.go +++ b/onpremise/permissionscheme.go @@ -31,6 +31,9 @@ type Holder struct { // GetList returns a list of all permission schemes // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *PermissionSchemeService) GetList(ctx context.Context) (*PermissionSchemes, *Response, error) { apiEndpoint := "/rest/api/3/permissionscheme" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -51,6 +54,9 @@ func (s *PermissionSchemeService) GetList(ctx context.Context) (*PermissionSchem // Get returns a full representation of the permission scheme for the schemeID // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-schemeId-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *PermissionSchemeService) Get(ctx context.Context, schemeID int) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/3/permissionscheme/%d", schemeID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/onpremise/priority.go b/onpremise/priority.go index dd0ff7e..20de68a 100644 --- a/onpremise/priority.go +++ b/onpremise/priority.go @@ -24,6 +24,9 @@ type Priority struct { // GetList gets all priorities from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-priority-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *PriorityService) GetList(ctx context.Context) ([]Priority, *Response, error) { apiEndpoint := "rest/api/2/priority" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/onpremise/project.go b/onpremise/project.go index cc03594..4e38da6 100644 --- a/onpremise/project.go +++ b/onpremise/project.go @@ -84,6 +84,9 @@ type PermissionScheme struct { // a list of all projects and their supported issuetypes. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-projects/#api-rest-api-2-project-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ProjectService) GetAll(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) { apiEndpoint := "rest/api/2/project" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -114,6 +117,9 @@ func (s *ProjectService) GetAll(ctx context.Context, options *GetQueryOptions) ( // This can be an project id, or an project key. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ProjectService) Get(ctx context.Context, projectID string) (*Project, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/project/%s", projectID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -136,6 +142,9 @@ func (s *ProjectService) Get(ctx context.Context, projectID string) (*Project, * // This can be an project id, or an project key. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ProjectService) GetPermissionScheme(ctx context.Context, projectID string) (*PermissionScheme, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s/permissionscheme", projectID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/onpremise/request.go b/onpremise/request.go index 6d12705..aac3c55 100644 --- a/onpremise/request.go +++ b/onpremise/request.go @@ -58,6 +58,9 @@ type RequestComment struct { // Create creates a new request. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (r *RequestService) Create(ctx context.Context, requester string, participants []string, request *Request) (*Request, *Response, error) { apiEndpoint := "rest/servicedeskapi/request" @@ -94,6 +97,9 @@ func (r *RequestService) Create(ctx context.Context, requester string, participa // CreateComment creates a comment on a request. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-comment-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (r *RequestService) CreateComment(ctx context.Context, issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/request/%v/comment", issueIDOrKey) diff --git a/onpremise/resolution.go b/onpremise/resolution.go index 0dc007a..cfca948 100644 --- a/onpremise/resolution.go +++ b/onpremise/resolution.go @@ -22,6 +22,9 @@ type Resolution struct { // GetList gets all resolutions from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-resolution-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ResolutionService) GetList(ctx context.Context) ([]Resolution, *Response, error) { apiEndpoint := "rest/api/2/resolution" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/onpremise/role.go b/onpremise/role.go index 08c6dc6..1ee873a 100644 --- a/onpremise/role.go +++ b/onpremise/role.go @@ -38,6 +38,9 @@ type ActorUser struct { // GetList returns a list of all available project roles // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *RoleService) GetList(ctx context.Context) (*[]Role, *Response, error) { apiEndpoint := "rest/api/3/role" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -56,6 +59,9 @@ func (s *RoleService) GetList(ctx context.Context) (*[]Role, *Response, error) { // Get retreives a single Role from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-id-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *RoleService) Get(ctx context.Context, roleID int) (*Role, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/3/role/%d", roleID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/onpremise/servicedesk.go b/onpremise/servicedesk.go index f3da1d9..239bedf 100644 --- a/onpremise/servicedesk.go +++ b/onpremise/servicedesk.go @@ -22,6 +22,9 @@ type ServiceDeskOrganizationDTO struct { // all organizations associated with a service desk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ServiceDeskService) GetOrganizations(ctx context.Context, serviceDeskID interface{}, start int, limit int, accountID string) (*PagedDTO, *Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization?start=%d&limit=%d", serviceDeskID, start, limit) if accountID != "" { @@ -52,6 +55,9 @@ func (s *ServiceDeskService) GetOrganizations(ctx context.Context, serviceDeskID // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-post // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ServiceDeskService) AddOrganization(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization", serviceDeskID) @@ -81,6 +87,9 @@ func (s *ServiceDeskService) AddOrganization(ctx context.Context, serviceDeskID // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-delete // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ServiceDeskService) RemoveOrganization(ctx context.Context, serviceDeskID interface{}, organizationID int) (*Response, error) { apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/organization", serviceDeskID) @@ -106,6 +115,9 @@ func (s *ServiceDeskService) RemoveOrganization(ctx context.Context, serviceDesk // AddCustomers adds customers to the given service desk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ServiceDeskService) AddCustomers(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) @@ -133,6 +145,9 @@ func (s *ServiceDeskService) AddCustomers(ctx context.Context, serviceDeskID int // RemoveCustomers removes customers to the given service desk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-delete +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ServiceDeskService) RemoveCustomers(ctx context.Context, serviceDeskID interface{}, acountIDs ...string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) @@ -160,6 +175,9 @@ func (s *ServiceDeskService) RemoveCustomers(ctx context.Context, serviceDeskID // ListCustomers lists customers for a ServiceDesk. // // https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-servicedeskid-customer-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *ServiceDeskService) ListCustomers(ctx context.Context, serviceDeskID interface{}, options *CustomerListOptions) (*CustomerList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%v/customer", serviceDeskID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/onpremise/sprint.go b/onpremise/sprint.go index 145f0e9..1553f6b 100644 --- a/onpremise/sprint.go +++ b/onpremise/sprint.go @@ -28,6 +28,9 @@ type IssuesInSprintResult struct { // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-moveIssuesToSprint // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *SprintService) MoveIssuesToSprint(ctx context.Context, sprintID int, issueIDs []string) (*Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) @@ -51,6 +54,9 @@ func (s *SprintService) MoveIssuesToSprint(ctx context.Context, sprintID int, is // By default, the returned issues are ordered by rank. // // Jira API Docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-getIssuesForSprint +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *SprintService) GetIssuesForSprint(ctx context.Context, sprintID int) ([]Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) @@ -79,6 +85,9 @@ func (s *SprintService) GetIssuesForSprint(ctx context.Context, sprintID int) ([ // Jira API docs: https://docs.atlassian.com/jira-software/REST/7.3.1/#agile/1.0/issue-getIssue // // TODO: create agile service for holding all agile apis' implementation +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *SprintService) GetIssue(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/issue/%s", issueID) diff --git a/onpremise/status.go b/onpremise/status.go index 3d9a92c..ff2f091 100644 --- a/onpremise/status.go +++ b/onpremise/status.go @@ -25,6 +25,9 @@ type Status struct { // GetAllStatuses returns a list of all statuses associated with workflows. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-status-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *StatusService) GetAllStatuses(ctx context.Context) ([]Status, *Response, error) { apiEndpoint := "rest/api/2/status" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) diff --git a/onpremise/user.go b/onpremise/user.go index b6205de..bcbe30a 100644 --- a/onpremise/user.go +++ b/onpremise/user.go @@ -47,6 +47,9 @@ type userSearchF func(userSearch) userSearch // Get gets user info from Jira using its Account Id // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *UserService) Get(ctx context.Context, accountId string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -66,6 +69,9 @@ func (s *UserService) Get(ctx context.Context, accountId string) (*User, *Respon // Searching by another parameter that is not accountId is deprecated, // but this method is kept for backwards compatibility // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *UserService) GetByAccountID(ctx context.Context, accountID string) (*User, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -84,6 +90,9 @@ func (s *UserService) GetByAccountID(ctx context.Context, accountID string) (*Us // Create creates an user in Jira. // // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-createUser +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *UserService) Create(ctx context.Context, user *User) (*User, *Response, error) { apiEndpoint := "/rest/api/2/user" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, user) @@ -111,6 +120,9 @@ func (s *UserService) Create(ctx context.Context, user *User) (*User, *Response, // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-delete // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *UserService) Delete(ctx context.Context, accountId string) (*Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId) req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) @@ -128,6 +140,9 @@ func (s *UserService) Delete(ctx context.Context, accountId string) (*Response, // GetGroups returns the groups which the user belongs to // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-groups-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *UserService) GetGroups(ctx context.Context, accountId string) (*[]UserGroup, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?accountId=%s", accountId) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -146,6 +161,9 @@ func (s *UserService) GetGroups(ctx context.Context, accountId string) (*[]UserG // GetSelf information about the current logged-in user // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-myself-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *UserService) GetSelf(ctx context.Context) (*User, *Response, error) { const apiEndpoint = "rest/api/2/myself" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -161,6 +179,9 @@ func (s *UserService) GetSelf(ctx context.Context) (*User, *Response, error) { } // WithMaxResults sets the max results to return +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func WithMaxResults(maxResults int) userSearchF { return func(s userSearch) userSearch { s = append(s, userSearchParam{name: "maxResults", value: fmt.Sprintf("%d", maxResults)}) @@ -169,6 +190,9 @@ func WithMaxResults(maxResults int) userSearchF { } // WithStartAt set the start pager +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func WithStartAt(startAt int) userSearchF { return func(s userSearch) userSearch { s = append(s, userSearchParam{name: "startAt", value: fmt.Sprintf("%d", startAt)}) @@ -177,6 +201,9 @@ func WithStartAt(startAt int) userSearchF { } // WithActive sets the active users lookup +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func WithActive(active bool) userSearchF { return func(s userSearch) userSearch { s = append(s, userSearchParam{name: "includeActive", value: fmt.Sprintf("%t", active)}) @@ -185,6 +212,9 @@ func WithActive(active bool) userSearchF { } // WithInactive sets the inactive users lookup +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func WithInactive(inactive bool) userSearchF { return func(s userSearch) userSearch { s = append(s, userSearchParam{name: "includeInactive", value: fmt.Sprintf("%t", inactive)}) @@ -193,6 +223,9 @@ func WithInactive(inactive bool) userSearchF { } // WithUsername sets the username to search +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func WithUsername(username string) userSearchF { return func(s userSearch) userSearch { s = append(s, userSearchParam{name: "username", value: username}) @@ -201,6 +234,9 @@ func WithUsername(username string) userSearchF { } // WithAccountId sets the account id to search +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func WithAccountId(accountId string) userSearchF { return func(s userSearch) userSearch { s = append(s, userSearchParam{name: "accountId", value: accountId}) @@ -209,6 +245,9 @@ func WithAccountId(accountId string) userSearchF { } // WithProperty sets the property (Property keys are specified by path) to search +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func WithProperty(property string) userSearchF { return func(s userSearch) userSearch { s = append(s, userSearchParam{name: "property", value: property}) @@ -220,6 +259,9 @@ func WithProperty(property string) userSearchF { // It can find users by email or display name using the query parameter // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-search-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *UserService) Find(ctx context.Context, property string, tweaks ...userSearchF) ([]User, *Response, error) { search := []userSearchParam{ { diff --git a/onpremise/version.go b/onpremise/version.go index 69e4bec..2a5418e 100644 --- a/onpremise/version.go +++ b/onpremise/version.go @@ -29,6 +29,9 @@ type Version struct { // Get gets version info from Jira // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *VersionService) Get(ctx context.Context, versionID int) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("/rest/api/2/version/%v", versionID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) @@ -47,6 +50,9 @@ func (s *VersionService) Get(ctx context.Context, versionID int) (*Version, *Res // Create creates a version in Jira. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-post +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *VersionService) Create(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := "/rest/api/2/version" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, version) @@ -73,6 +79,9 @@ func (s *VersionService) Create(ctx context.Context, version *Version) (*Version // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-put // Caller must close resp.Body +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. func (s *VersionService) Update(ctx context.Context, version *Version) (*Version, *Response, error) { apiEndpoint := fmt.Sprintf("rest/api/2/version/%v", version.ID) req, err := s.client.NewRequest(ctx, http.MethodPut, apiEndpoint, version) From 5d84a841642354799be8e3b344e91f8c7d689569 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 19:53:18 +0200 Subject: [PATCH 125/189] Cloud/Components: Add ComponentService.Get Thanks goes to @krrishd for his early work in https://github.com/andygrunwald/go-jira/pull/389 --- cloud/component.go | 20 +++++++++++ cloud/component_test.go | 49 +++++++++++++++++++++++++++ testing/mock-data/component_get.json | 50 ++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 testing/mock-data/component_get.json diff --git a/cloud/component.go b/cloud/component.go index bac214c..66eb604 100644 --- a/cloud/component.go +++ b/cloud/component.go @@ -2,6 +2,7 @@ package cloud import ( "context" + "fmt" "net/http" ) @@ -41,3 +42,22 @@ func (s *ComponentService) Create(ctx context.Context, options *CreateComponentO return component, resp, nil } + +// Get returns a component for the given componentID. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-rest-api-3-component-id-get +func (s *ComponentService) Get(ctx context.Context, componentID string) (*ProjectComponent, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/3/component/%s", componentID) + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + component := new(ProjectComponent) + resp, err := s.client.Do(req, component) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + + return component, resp, nil +} diff --git a/cloud/component_test.go b/cloud/component_test.go index 780cfdb..9586e0b 100644 --- a/cloud/component_test.go +++ b/cloud/component_test.go @@ -3,6 +3,7 @@ package cloud import ( "context" "fmt" + "io/ioutil" "net/http" "testing" ) @@ -28,3 +29,51 @@ func TestComponentService_Create_Success(t *testing.T) { t.Errorf("Error given: %s", err) } } + +func TestComponentService_Get(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/3/component/42102" + + raw, err := ioutil.ReadFile("../testing/mock-data/component_get.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + component, _, err := testClient.Component.Get(context.Background(), "42102") + if err != nil { + t.Errorf("Error given: %s", err) + } + if component == nil { + t.Error("Expected component. Component is nil") + } +} + +func TestComponentService_Get_NoComponent(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/3/component/99999999" + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, nil) + }) + + component, resp, err := testClient.Component.Get(context.Background(), "99999999") + + if component != nil { + t.Errorf("Expected nil. Got %+v", component) + } + if resp.Status == "404" { + t.Errorf("Expected status 404. Got %s", resp.Status) + } + if err == nil { + t.Error("No error given. Expected one") + } +} diff --git a/testing/mock-data/component_get.json b/testing/mock-data/component_get.json new file mode 100644 index 0000000..cda055d --- /dev/null +++ b/testing/mock-data/component_get.json @@ -0,0 +1,50 @@ +{ + "self": "https://issues.apache.org/jira/rest/api/2/component/42102", + "id": "42102", + "name": "Some Component", + "lead": { + "self": "https://issues.apache.org/jira/rest/api/2/user?username=firstname.lastname@apache.org", + "key": "firstname.lastname", + "name": "firstname.lastname@apache.org", + "avatarUrls": { + "48x48": "https://issues.apache.org/jira/secure/useravatar?ownerId=firstname.lastname&avatarId=31851", + "24x24": "https://issues.apache.org/jira/secure/useravatar?size=small&ownerId=firstname.lastname&avatarId=31851", + "16x16": "https://issues.apache.org/jira/secure/useravatar?size=xsmall&ownerId=firstname.lastname&avatarId=31851", + "32x32": "https://issues.apache.org/jira/secure/useravatar?size=medium&ownerId=firstname.lastname&avatarId=31851" + }, + "displayName": "Firstname Lastname", + "active": true + }, + "assigneeType": "COMPONENT_LEAD", + "assignee": { + "self": "https://issues.apache.org/jira/rest/api/2/user?username=firstname.lastname@apache.org", + "key": "firstname.lastname", + "name": "firstname.lastname@apache.org", + "avatarUrls": { + "48x48": "https://issues.apache.org/jira/secure/useravatar?ownerId=firstname.lastname&avatarId=31851", + "24x24": "https://issues.apache.org/jira/secure/useravatar?size=small&ownerId=firstname.lastname&avatarId=31851", + "16x16": "https://issues.apache.org/jira/secure/useravatar?size=xsmall&ownerId=firstname.lastname&avatarId=31851", + "32x32": "https://issues.apache.org/jira/secure/useravatar?size=medium&ownerId=firstname.lastname&avatarId=31851" + }, + "displayName": "Firstname Lastname", + "active": true + }, + "realAssigneeType": "COMPONENT_LEAD", + "realAssignee": { + "self": "https://issues.apache.org/jira/rest/api/2/user?username=firstname.lastname@apache.org", + "key": "firstname.lastname", + "name": "firstname.lastname@apache.org", + "avatarUrls": { + "48x48": "https://issues.apache.org/jira/secure/useravatar?ownerId=firstname.lastname&avatarId=31851", + "24x24": "https://issues.apache.org/jira/secure/useravatar?size=small&ownerId=firstname.lastname&avatarId=31851", + "16x16": "https://issues.apache.org/jira/secure/useravatar?size=xsmall&ownerId=firstname.lastname&avatarId=31851", + "32x32": "https://issues.apache.org/jira/secure/useravatar?size=medium&ownerId=firstname.lastname&avatarId=31851" + }, + "displayName": "Firstname Lastname", + "active": true + }, + "isAssigneeTypeValid": true, + "project": "ABC", + "projectId": 12345, + "archived": false + } \ No newline at end of file From 28d994ec56a80f6721f4de79d3be164df90b2452 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 20:00:37 +0200 Subject: [PATCH 126/189] Fix package io/ioutil is deprecated: As of Go 1.16, the same functionality is now provided by package io or package os, and those implementations should be preferred in new code. See the specific function documentation for details. (SA1019) --- cloud/component_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud/component_test.go b/cloud/component_test.go index 9586e0b..4bba036 100644 --- a/cloud/component_test.go +++ b/cloud/component_test.go @@ -3,8 +3,8 @@ package cloud import ( "context" "fmt" - "io/ioutil" "net/http" + "os" "testing" ) @@ -35,7 +35,7 @@ func TestComponentService_Get(t *testing.T) { defer teardown() testAPIEndpoint := "/rest/api/3/component/42102" - raw, err := ioutil.ReadFile("../testing/mock-data/component_get.json") + raw, err := os.ReadFile("../testing/mock-data/component_get.json") if err != nil { t.Error(err.Error()) } From aeff901419eec3ebe78171a8cc535433bba52d27 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 20:38:58 +0200 Subject: [PATCH 127/189] Cloud/Component: Review service "Component" in the Cloud Jira client for latest API changes --- CHANGELOG.md | 1 + cloud/component.go | 75 +++++++++++++++++++------ cloud/component_test.go | 8 ++- cloud/examples/component_create/main.go | 36 ++++++++++++ cloud/examples/component_get/main.go | 31 ++++++++++ 5 files changed, 131 insertions(+), 20 deletions(-) create mode 100644 cloud/examples/component_create/main.go create mode 100644 cloud/examples/component_get/main.go diff --git a/CHANGELOG.md b/CHANGELOG.md index fafcabf..6a8deeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -396,6 +396,7 @@ client, err := jira.NewClient("https://...", tp.Client()) * Cloud/Authentication: Removed `PATAuthTransport`, because it was a (kind of) duplicate of `BasicAuthTransport` * Cloud/Authentication: `BasicAuthTransport.Password` was renamed to `BasicAuthTransport.APIToken` * Cloud/Authentication: Removes `CookieAuthTransport` and `AuthenticationService`, because this type of auth is not supported by the Jira cloud offering +* Cloud/Component: The type `CreateComponentOptions` was renamed to `ComponentCreateOptions` ### Features diff --git a/cloud/component.go b/cloud/component.go index 66eb604..61b25b9 100644 --- a/cloud/component.go +++ b/cloud/component.go @@ -6,28 +6,60 @@ import ( "net/http" ) -// ComponentService handles components for the Jira instance / API.// -// Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.1/#api/2/component +// ComponentService represents project components. +// Use it to get, create, update, and delete project components. +// Also get components for project and get a count of issues by component. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-group-project-components type ComponentService service -// CreateComponentOptions are passed to the ComponentService.Create function to create a new Jira component -type CreateComponentOptions struct { - Name string `json:"name,omitempty" structs:"name,omitempty"` - Description string `json:"description,omitempty" structs:"description,omitempty"` - Lead *User `json:"lead,omitempty" structs:"lead,omitempty"` - LeadUserName string `json:"leadUserName,omitempty" structs:"leadUserName,omitempty"` +const ( + AssigneeTypeProjectLead = "PROJECT_LEAD" + AssigneeTypeComponentLead = "COMPONENT_LEAD" + AssigneeTypeUnassigned = "UNASSIGNED" + AssigneeTypeProjectDefault = "PROJECT_DEFAULT" +) + +// ComponentCreateOptions are passed to the ComponentService.Create function to create a new Jira component +type ComponentCreateOptions struct { + // Name: The unique name for the component in the project. + // Required when creating a component. + // Optional when updating a component. + // The maximum length is 255 characters. + Name string `json:"name,omitempty" structs:"name,omitempty"` + + // Description: The description for the component. + // Optional when creating or updating a component. + Description string `json:"description,omitempty" structs:"description,omitempty"` + + // LeadAccountId: The accountId of the component's lead user. + // The accountId uniquely identifies the user across all Atlassian products. + // For example, 5b10ac8d82e05b22cc7d4ef5. + LeadAccountId string `json:"leadAccountId,omitempty" structs:"leadAccountId,omitempty"` + + // AssigneeType: The nominal user type used to determine the assignee for issues created with this component. + // Can take the following values: + // PROJECT_LEAD the assignee to any issues created with this component is nominally the lead for the project the component is in. + // COMPONENT_LEAD the assignee to any issues created with this component is nominally the lead for the component. + // UNASSIGNED an assignee is not set for issues created with this component. + // PROJECT_DEFAULT the assignee to any issues created with this component is nominally the default assignee for the project that the component is in. + // + // Default value: PROJECT_DEFAULT. + // Optional when creating or updating a component. AssigneeType string `json:"assigneeType,omitempty" structs:"assigneeType,omitempty"` - Assignee *User `json:"assignee,omitempty" structs:"assignee,omitempty"` - Project string `json:"project,omitempty" structs:"project,omitempty"` - ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"` + + // Project: The key of the project the component is assigned to. + // Required when creating a component. + // Can't be updated. + Project string `json:"project,omitempty" structs:"project,omitempty"` } -// Create creates a new Jira component based on the given options. +// Create creates a component. +// Use components to provide containers for issues within a project. // -// TODO Double check this method if this works as expected, is using the latest API and the response is complete -// This double check effort is done for v2 - Remove this two lines if this is completed. -func (s *ComponentService) Create(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) { - apiEndpoint := "rest/api/2/component" +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-rest-api-3-component-post +func (s *ComponentService) Create(ctx context.Context, options *ComponentCreateOptions) (*ProjectComponent, *Response, error) { + apiEndpoint := "rest/api/3/component" req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, options) if err != nil { return nil, nil, err @@ -35,7 +67,6 @@ func (s *ComponentService) Create(ctx context.Context, options *CreateComponentO component := new(ProjectComponent) resp, err := s.client.Do(req, component) - if err != nil { return nil, resp, NewJiraError(resp, err) } @@ -61,3 +92,13 @@ func (s *ComponentService) Get(ctx context.Context, componentID string) (*Projec return component, resp, nil } + +// TODO Add "Update component" method. See https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-rest-api-3-component-id-put + +// TODO Add "Delete component" method. See https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-rest-api-3-component-id-delete + +// TODO Add "Get component issues count" method. See https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-rest-api-3-component-id-relatedissuecounts-get + +// TODO Add "Get project components paginated" method. See https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-rest-api-3-project-projectidorkey-component-get + +// TODO Add "Get project components" method. See https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-rest-api-3-project-projectidorkey-components-get diff --git a/cloud/component_test.go b/cloud/component_test.go index 4bba036..3c8305a 100644 --- a/cloud/component_test.go +++ b/cloud/component_test.go @@ -11,15 +11,17 @@ import ( func TestComponentService_Create_Success(t *testing.T) { setup() defer teardown() - testMux.HandleFunc("/rest/api/2/component", func(w http.ResponseWriter, r *http.Request) { + testAPIEndpoint := "/rest/api/3/component" + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodPost) - testRequestURL(t, r, "/rest/api/2/component") + testRequestURL(t, r, testAPIEndpoint) w.WriteHeader(http.StatusCreated) fmt.Fprint(w, `{ "self": "http://www.example.com/jira/rest/api/2/component/10000", "id": "10000", "name": "Component 1", "description": "This is a Jira component", "lead": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "avatarUrls": { "48x48": "http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", "24x24": "http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred", "16x16": "http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", "32x32": "http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred" }, "displayName": "Fred F. User", "active": false }, "assigneeType": "PROJECT_LEAD", "assignee": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "avatarUrls": { "48x48": "http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", "24x24": "http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred", "16x16": "http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", "32x32": "http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred" }, "displayName": "Fred F. User", "active": false }, "realAssigneeType": "PROJECT_LEAD", "realAssignee": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "avatarUrls": { "48x48": "http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", "24x24": "http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred", "16x16": "http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", "32x32": "http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred" }, "displayName": "Fred F. User", "active": false }, "isAssigneeTypeValid": false, "project": "HSP", "projectId": 10000 }`) }) - component, _, err := testClient.Component.Create(context.Background(), &CreateComponentOptions{ + component, _, err := testClient.Component.Create(context.Background(), &ComponentCreateOptions{ Name: "foo-bar", }) if component == nil { diff --git a/cloud/examples/component_create/main.go b/cloud/examples/component_create/main.go new file mode 100644 index 0000000..5f40a61 --- /dev/null +++ b/cloud/examples/component_create/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "context" + "fmt" + + jira "github.com/andygrunwald/go-jira/v2/cloud" +) + +func main() { + jiraURL := "https://go-jira-opensource.atlassian.net/" + + // Jira docs: https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/ + // Create a new API token: https://id.atlassian.com/manage-profile/security/api-tokens + tp := jira.BasicAuthTransport{ + Username: "", + APIToken: "", + } + client, err := jira.NewClient(jiraURL, tp.Client()) + if err != nil { + panic(err) + } + + c := &jira.ComponentCreateOptions{ + Name: "Dummy component", + AssigneeType: jira.AssigneeTypeUnassigned, + Project: "BUG", + } + component, _, err := client.Component.Create(context.Background(), c) + if err != nil { + panic(err) + } + + fmt.Printf("component: %+v\n", component) + fmt.Println("Success!") +} diff --git a/cloud/examples/component_get/main.go b/cloud/examples/component_get/main.go new file mode 100644 index 0000000..187cf40 --- /dev/null +++ b/cloud/examples/component_get/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "context" + "fmt" + + jira "github.com/andygrunwald/go-jira/v2/cloud" +) + +func main() { + jiraURL := "https://go-jira-opensource.atlassian.net/" + + // Jira docs: https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/ + // Create a new API token: https://id.atlassian.com/manage-profile/security/api-tokens + tp := jira.BasicAuthTransport{ + Username: "", + APIToken: "", + } + client, err := jira.NewClient(jiraURL, tp.Client()) + if err != nil { + panic(err) + } + + component, _, err := client.Component.Get(context.Background(), "10000") + if err != nil { + panic(err) + } + + fmt.Printf("component: %+v\n", component) + fmt.Println("Success!") +} From 685151c03f0180c0f4cf257a1c311187a64939ad Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 14:12:39 +0200 Subject: [PATCH 128/189] Cloud/User: Update User struct Thanks goes to @t-junjie for his early work in https://github.com/andygrunwald/go-jira/pull/412 --- cloud/examples/basic_auth/main.go | 2 +- cloud/user.go | 71 +++++++++++----- cloud/user_test.go | 133 +++++++++++++++++++++++++----- 3 files changed, 164 insertions(+), 42 deletions(-) diff --git a/cloud/examples/basic_auth/main.go b/cloud/examples/basic_auth/main.go index ab37284..bafa6a0 100644 --- a/cloud/examples/basic_auth/main.go +++ b/cloud/examples/basic_auth/main.go @@ -21,7 +21,7 @@ func main() { panic(err) } - u, _, err := client.User.GetSelf(context.Background()) + u, _, err := client.User.GetCurrentUser(context.Background()) if err != nil { panic(err) } diff --git a/cloud/user.go b/cloud/user.go index 8fa8536..3453a72 100644 --- a/cloud/user.go +++ b/cloud/user.go @@ -14,19 +14,20 @@ type UserService service // User represents a Jira user. type User struct { - Self string `json:"self,omitempty" structs:"self,omitempty"` - AccountID string `json:"accountId,omitempty" structs:"accountId,omitempty"` - AccountType string `json:"accountType,omitempty" structs:"accountType,omitempty"` - Name string `json:"name,omitempty" structs:"name,omitempty"` - Key string `json:"key,omitempty" structs:"key,omitempty"` - Password string `json:"-"` - EmailAddress string `json:"emailAddress,omitempty" structs:"emailAddress,omitempty"` - AvatarUrls AvatarUrls `json:"avatarUrls,omitempty" structs:"avatarUrls,omitempty"` - DisplayName string `json:"displayName,omitempty" structs:"displayName,omitempty"` - Active bool `json:"active,omitempty" structs:"active,omitempty"` - TimeZone string `json:"timeZone,omitempty" structs:"timeZone,omitempty"` - Locale string `json:"locale,omitempty" structs:"locale,omitempty"` - ApplicationKeys []string `json:"applicationKeys,omitempty" structs:"applicationKeys,omitempty"` + Self string `json:"self,omitempty" structs:"self,omitempty"` + AccountID string `json:"accountId,omitempty" structs:"accountId,omitempty"` + AccountType string `json:"accountType,omitempty" structs:"accountType,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + Key string `json:"key,omitempty" structs:"key,omitempty"` + Password string `json:"-"` + EmailAddress string `json:"emailAddress,omitempty" structs:"emailAddress,omitempty"` + AvatarUrls AvatarUrls `json:"avatarUrls,omitempty" structs:"avatarUrls,omitempty"` + DisplayName string `json:"displayName,omitempty" structs:"displayName,omitempty"` + Active bool `json:"active,omitempty" structs:"active,omitempty"` + TimeZone string `json:"timeZone,omitempty" structs:"timeZone,omitempty"` + Locale string `json:"locale,omitempty" structs:"locale,omitempty"` + Groups UserGroups `json:"groups,omitempty" structs:"groups,omitempty"` + ApplicationRoles ApplicationRoles `json:"applicationRoles,omitempty" structs:"applicationRoles,omitempty"` } // UserGroup represents the group list @@ -35,6 +36,37 @@ type UserGroup struct { Name string `json:"name,omitempty" structs:"name,omitempty"` } +// Groups is a wrapper for UserGroup +type UserGroups struct { + Size int `json:"size,omitempty" structs:"size,omitempty"` + Items []UserGroup `json:"items,omitempty" structs:"items,omitempty"` +} + +// ApplicationRoles is a wrapper for ApplicationRole +type ApplicationRoles struct { + Size int `json:"size,omitempty" structs:"size,omitempty"` + Items []ApplicationRole `json:"items,omitempty" structs:"items,omitempty"` +} + +// ApplicationRole represents a role assigned to a user +type ApplicationRole struct { + Key string `json:"key"` + Groups []string `json:"groups"` + Name string `json:"name"` + DefaultGroups []string `json:"defaultGroups"` + SelectedByDefault bool `json:"selectedByDefault"` + Defined bool `json:"defined"` + NumberOfSeats int `json:"numberOfSeats"` + RemainingSeats int `json:"remainingSeats"` + UserCount int `json:"userCount"` + UserCountDescription string `json:"userCountDescription"` + HasUnlimitedSeats bool `json:"hasUnlimitedSeats"` + Platform bool `json:"platform"` + + // Key `groupDetails` missing - https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-application-roles/#api-rest-api-3-applicationrole-key-get + // Key `defaultGroupsDetails` missing - https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-application-roles/#api-rest-api-3-applicationrole-key-get +} + type userSearchParam struct { name string value string @@ -158,23 +190,22 @@ func (s *UserService) GetGroups(ctx context.Context, accountId string) (*[]UserG return userGroups, resp, nil } -// GetSelf information about the current logged-in user -// -// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-myself-get +// GetCurrentUser returns details for the current user. // -// TODO Double check this method if this works as expected, is using the latest API and the response is complete -// This double check effort is done for v2 - Remove this two lines if this is completed. -func (s *UserService) GetSelf(ctx context.Context) (*User, *Response, error) { - const apiEndpoint = "rest/api/2/myself" +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-myself/#api-rest-api-3-myself-get +func (s *UserService) GetCurrentUser(ctx context.Context) (*User, *Response, error) { + const apiEndpoint = "rest/api/3/myself" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err } + var user User resp, err := s.client.Do(req, &user) if err != nil { return nil, resp, NewJiraError(resp, err) } + return &user, resp, nil } diff --git a/cloud/user_test.go b/cloud/user_test.go index 192688e..7e5fdb2 100644 --- a/cloud/user_test.go +++ b/cloud/user_test.go @@ -61,16 +61,89 @@ func TestUserService_Create(t *testing.T) { testRequestURL(t, r, "/rest/api/2/user") w.WriteHeader(http.StatusCreated) - fmt.Fprint(w, `{"name":"charlie","password":"abracadabra","emailAddress":"charlie@atlassian.com", - "displayName":"Charlie of Atlassian","applicationKeys":["jira-core"]}`) + fmt.Fprint(w, ` + { + "name": "charlie", + "password": "abracadabra", + "emailAddress": "charlie@atlassian.com", + "displayName": "Charlie of Atlassian", + "applicationRoles": { + "size": 1, + "max-results": 1, + "items": [{ + "key": "jira-software", + "groups": [ + "jira-software-users", + "jira-testers" + ], + "name": "Jira Software", + "defaultGroups": [ + "jira-software-users" + ], + "selectedByDefault": false, + "defined": false, + "numberOfSeats": 10, + "remainingSeats": 5, + "userCount": 5, + "userCountDescription": "5 developers", + "hasUnlimitedSeats": false, + "platform": false + }] + }, + "groups": { + "size": 2, + "max-results": 2, + "items": [{ + "name": "jira-core", + "self": "jira-core" + }, + { + "name": "jira-test", + "self": "jira-test" + } + ] + } + } + `) }) u := &User{ - Name: "charlie", - Password: "abracadabra", - EmailAddress: "charlie@atlassian.com", - DisplayName: "Charlie of Atlassian", - ApplicationKeys: []string{"jira-core"}, + Name: "charlie", + Password: "abracadabra", + EmailAddress: "charlie@atlassian.com", + DisplayName: "Charlie of Atlassian", + Groups: UserGroups{ + Size: 2, + Items: []UserGroup{ + { + Name: "jira-core", + Self: "jira-core", + }, + { + Name: "jira-test", + Self: "jira-test", + }, + }, + }, + ApplicationRoles: ApplicationRoles{ + Size: 1, + Items: []ApplicationRole{ + { + Key: "jira-software", + Groups: []string{"jira-software-users", "jira-testers"}, + Name: "Jira Software", + DefaultGroups: []string{"jira-software-users"}, + SelectedByDefault: false, + Defined: false, + NumberOfSeats: 10, + RemainingSeats: 5, + UserCount: 5, + UserCountDescription: "5 developers", + HasUnlimitedSeats: false, + Platform: false, + }, + }, + }, } if user, _, err := testClient.User.Create(context.Background(), u); err != nil { @@ -118,30 +191,48 @@ func TestUserService_GetGroups(t *testing.T) { } } -func TestUserService_GetSelf(t *testing.T) { +func TestUserService_GetCurrentUser(t *testing.T) { setup() defer teardown() - testMux.HandleFunc("/rest/api/2/myself", func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc("/rest/api/3/myself", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, "/rest/api/2/myself") + testRequestURL(t, r, "/rest/api/3/myself") w.WriteHeader(http.StatusCreated) - fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","key":"fred", - "name":"fred","emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", - "24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", - "32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":true,"timeZone":"Australia/Sydney","groups":{"size":3,"items":[ - {"name":"jira-user","self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-user"},{"name":"jira-admin", - "self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-admin"},{"name":"important","self":"http://www.example.com/jira/rest/api/2/group?groupname=important" - }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) + fmt.Fprint(w, `{ + "self": "https://your-domain.atlassian.net/rest/api/3/user?accountId=5b10a2844c20165700ede21g", + "key": "", + "accountId": "5b10a2844c20165700ede21g", + "accountType": "atlassian", + "name": "", + "emailAddress": "mia@example.com", + "avatarUrls": { + "48x48": "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=48&s=48", + "24x24": "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=24&s=24", + "16x16": "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=16&s=16", + "32x32": "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=32&s=32" + }, + "displayName": "Mia Krystof", + "active": true, + "timeZone": "Australia/Sydney", + "groups": { + "size": 3, + "items": [] + }, + "applicationRoles": { + "size": 1, + "items": [] + } + }`) }) - if user, _, err := testClient.User.GetSelf(context.Background()); err != nil { + if user, _, err := testClient.User.GetCurrentUser(context.Background()); err != nil { t.Errorf("Error given: %s", err) + } else if user == nil { t.Error("Expected user groups. []UserGroup is nil") - } else if user.Name != "fred" || - !user.Active || - user.DisplayName != "Fred F. User" { + + } else if user.EmailAddress != "mia@example.com" || !user.Active || user.DisplayName != "Mia Krystof" { t.Errorf("User JSON deserialized incorrectly") } } From 2b871464c1da9f3359af365c170fe68f1219e200 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 21:05:08 +0200 Subject: [PATCH 129/189] Fixed typo: apiEdpoint vs. apiEndpoint --- cloud/board_test.go | 18 +++++++++--------- cloud/field_test.go | 6 +++--- cloud/permissionscheme_test.go | 12 ++++++------ cloud/priority_test.go | 6 +++--- cloud/project_test.go | 28 ++++++++++++++-------------- cloud/resolution_test.go | 6 +++--- cloud/role_test.go | 12 ++++++------ cloud/sprint_test.go | 6 +++--- cloud/status_test.go | 6 +++--- onpremise/board_test.go | 18 +++++++++--------- onpremise/field_test.go | 6 +++--- onpremise/permissionscheme_test.go | 12 ++++++------ onpremise/priority_test.go | 6 +++--- onpremise/project_test.go | 28 ++++++++++++++-------------- onpremise/resolution_test.go | 6 +++--- onpremise/role_test.go | 12 ++++++------ onpremise/sprint_test.go | 6 +++--- onpremise/status_test.go | 6 +++--- 18 files changed, 100 insertions(+), 100 deletions(-) diff --git a/cloud/board_test.go b/cloud/board_test.go index 099c424..c37978a 100644 --- a/cloud/board_test.go +++ b/cloud/board_test.go @@ -11,15 +11,15 @@ import ( func TestBoardService_GetAllBoards(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/agile/1.0/board" + testapiEndpoint := "/rest/agile/1.0/board" raw, err := os.ReadFile("../testing/mock-data/all_boards.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, string(raw)) }) @@ -36,15 +36,15 @@ func TestBoardService_GetAllBoards(t *testing.T) { func TestBoardService_GetAllBoards_WithFilter(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/agile/1.0/board" + testapiEndpoint := "/rest/agile/1.0/board" raw, err := os.ReadFile("../testing/mock-data/all_boards_filtered.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) testRequestParams(t, r, map[string]string{"type": "scrum", "name": "Test", "startAt": "1", "maxResults": "10", "projectKeyOrId": "TE"}) fmt.Fprint(w, string(raw)) }) @@ -69,11 +69,11 @@ func TestBoardService_GetAllBoards_WithFilter(t *testing.T) { func TestBoardService_GetBoard(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/agile/1.0/board/1" + testapiEndpoint := "/rest/agile/1.0/board/1" - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, `{"id":4,"self":"https://test.jira.org/rest/agile/1.0/board/1","name":"Test Weekly","type":"scrum"}`) }) diff --git a/cloud/field_test.go b/cloud/field_test.go index c6f9fe8..1aa1305 100644 --- a/cloud/field_test.go +++ b/cloud/field_test.go @@ -11,15 +11,15 @@ import ( func TestFieldService_GetList(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/field" + testapiEndpoint := "/rest/api/2/field" raw, err := os.ReadFile("../testing/mock-data/all_fields.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, string(raw)) }) diff --git a/cloud/permissionscheme_test.go b/cloud/permissionscheme_test.go index 4503289..6074774 100644 --- a/cloud/permissionscheme_test.go +++ b/cloud/permissionscheme_test.go @@ -63,14 +63,14 @@ func TestPermissionSchemeService_GetList_NoList(t *testing.T) { func TestPermissionSchemeService_Get(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/3/permissionscheme/10100" + testapiEndpoint := "/rest/api/3/permissionscheme/10100" raw, err := os.ReadFile("../testing/mock-data/permissionscheme.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(writer http.ResponseWriter, request *http.Request) { testMethod(t, request, http.MethodGet) - testRequestURL(t, request, testAPIEdpoint) + testRequestURL(t, request, testapiEndpoint) fmt.Fprint(writer, string(raw)) }) @@ -86,14 +86,14 @@ func TestPermissionSchemeService_Get(t *testing.T) { func TestPermissionSchemeService_Get_NoScheme(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/3/permissionscheme/99999" + testapiEndpoint := "/rest/api/3/permissionscheme/99999" raw, err := os.ReadFile("../testing/mock-data/no_permissionscheme.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(writer http.ResponseWriter, request *http.Request) { testMethod(t, request, http.MethodGet) - testRequestURL(t, request, testAPIEdpoint) + testRequestURL(t, request, testapiEndpoint) fmt.Fprint(writer, string(raw)) }) diff --git a/cloud/priority_test.go b/cloud/priority_test.go index 6fa6f23..2c5d21f 100644 --- a/cloud/priority_test.go +++ b/cloud/priority_test.go @@ -11,15 +11,15 @@ import ( func TestPriorityService_GetList(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/priority" + testapiEndpoint := "/rest/api/2/priority" raw, err := os.ReadFile("../testing/mock-data/all_priorities.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, string(raw)) }) diff --git a/cloud/project_test.go b/cloud/project_test.go index 046df3d..d3c877c 100644 --- a/cloud/project_test.go +++ b/cloud/project_test.go @@ -11,13 +11,13 @@ import ( func TestProjectService_GetAll(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/project" + testapiEndpoint := "/rest/api/2/project" raw, err := os.ReadFile("../testing/mock-data/all_projects.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/project?expand=issueTypes") fmt.Fprint(w, string(raw)) @@ -35,15 +35,15 @@ func TestProjectService_GetAll(t *testing.T) { func TestProjectService_Get(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/project/12310505" + testapiEndpoint := "/rest/api/2/project/12310505" raw, err := os.ReadFile("../testing/mock-data/project.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, string(raw)) }) @@ -63,11 +63,11 @@ func TestProjectService_Get(t *testing.T) { func TestProjectService_Get_NoProject(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/project/99999999" + testapiEndpoint := "/rest/api/2/project/99999999" - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, nil) }) @@ -87,11 +87,11 @@ func TestProjectService_Get_NoProject(t *testing.T) { func TestProjectService_GetPermissionScheme_Failure(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/project/99999999/permissionscheme" + testapiEndpoint := "/rest/api/2/project/99999999/permissionscheme" - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, nil) }) @@ -111,11 +111,11 @@ func TestProjectService_GetPermissionScheme_Failure(t *testing.T) { func TestProjectService_GetPermissionScheme_Success(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/project/99999999/permissionscheme" + testapiEndpoint := "/rest/api/2/project/99999999/permissionscheme" - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, `{ "expand": "permissions,user,group,projectRole,field,all", "id": 10201, diff --git a/cloud/resolution_test.go b/cloud/resolution_test.go index d003e31..fdc5804 100644 --- a/cloud/resolution_test.go +++ b/cloud/resolution_test.go @@ -11,15 +11,15 @@ import ( func TestResolutionService_GetList(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/resolution" + testapiEndpoint := "/rest/api/2/resolution" raw, err := os.ReadFile("../testing/mock-data/all_resolutions.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, string(raw)) }) diff --git a/cloud/role_test.go b/cloud/role_test.go index 506f1a7..20f7e8d 100644 --- a/cloud/role_test.go +++ b/cloud/role_test.go @@ -64,14 +64,14 @@ func TestRoleService_GetList(t *testing.T) { func TestRoleService_Get_NoRole(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/3/role/99999" + testapiEndpoint := "/rest/api/3/role/99999" raw, err := os.ReadFile("../testing/mock-data/no_role.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(writer http.ResponseWriter, request *http.Request) { testMethod(t, request, http.MethodGet) - testRequestURL(t, request, testAPIEdpoint) + testRequestURL(t, request, testapiEndpoint) fmt.Fprint(writer, string(raw)) }) @@ -87,14 +87,14 @@ func TestRoleService_Get_NoRole(t *testing.T) { func TestRoleService_Get(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/3/role/10002" + testapiEndpoint := "/rest/api/3/role/10002" raw, err := os.ReadFile("../testing/mock-data/role.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(writer http.ResponseWriter, request *http.Request) { testMethod(t, request, http.MethodGet) - testRequestURL(t, request, testAPIEdpoint) + testRequestURL(t, request, testapiEndpoint) fmt.Fprint(writer, string(raw)) }) diff --git a/cloud/sprint_test.go b/cloud/sprint_test.go index 67801ad..dbc02de 100644 --- a/cloud/sprint_test.go +++ b/cloud/sprint_test.go @@ -43,15 +43,15 @@ func TestSprintService_MoveIssuesToSprint(t *testing.T) { func TestSprintService_GetIssuesForSprint(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/agile/1.0/sprint/123/issue" + testapiEndpoint := "/rest/agile/1.0/sprint/123/issue" raw, err := os.ReadFile("../testing/mock-data/issues_in_sprint.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, string(raw)) }) diff --git a/cloud/status_test.go b/cloud/status_test.go index c433a68..c217ecf 100644 --- a/cloud/status_test.go +++ b/cloud/status_test.go @@ -11,16 +11,16 @@ import ( func TestStatusService_GetAllStatuses(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/status" + testapiEndpoint := "/rest/api/2/status" raw, err := os.ReadFile("../testing/mock-data/all_statuses.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, string(raw)) }) diff --git a/onpremise/board_test.go b/onpremise/board_test.go index 894876b..44e3e12 100644 --- a/onpremise/board_test.go +++ b/onpremise/board_test.go @@ -11,15 +11,15 @@ import ( func TestBoardService_GetAllBoards(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/agile/1.0/board" + testapiEndpoint := "/rest/agile/1.0/board" raw, err := os.ReadFile("../testing/mock-data/all_boards.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, string(raw)) }) @@ -36,15 +36,15 @@ func TestBoardService_GetAllBoards(t *testing.T) { func TestBoardService_GetAllBoards_WithFilter(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/agile/1.0/board" + testapiEndpoint := "/rest/agile/1.0/board" raw, err := os.ReadFile("../testing/mock-data/all_boards_filtered.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) testRequestParams(t, r, map[string]string{"type": "scrum", "name": "Test", "startAt": "1", "maxResults": "10", "projectKeyOrId": "TE"}) fmt.Fprint(w, string(raw)) }) @@ -69,11 +69,11 @@ func TestBoardService_GetAllBoards_WithFilter(t *testing.T) { func TestBoardService_GetBoard(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/agile/1.0/board/1" + testapiEndpoint := "/rest/agile/1.0/board/1" - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, `{"id":4,"self":"https://test.jira.org/rest/agile/1.0/board/1","name":"Test Weekly","type":"scrum"}`) }) diff --git a/onpremise/field_test.go b/onpremise/field_test.go index f89b46e..ae8ed4f 100644 --- a/onpremise/field_test.go +++ b/onpremise/field_test.go @@ -11,15 +11,15 @@ import ( func TestFieldService_GetList(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/field" + testapiEndpoint := "/rest/api/2/field" raw, err := os.ReadFile("../testing/mock-data/all_fields.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, string(raw)) }) diff --git a/onpremise/permissionscheme_test.go b/onpremise/permissionscheme_test.go index ace1aa9..1b731ab 100644 --- a/onpremise/permissionscheme_test.go +++ b/onpremise/permissionscheme_test.go @@ -63,14 +63,14 @@ func TestPermissionSchemeService_GetList_NoList(t *testing.T) { func TestPermissionSchemeService_Get(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/3/permissionscheme/10100" + testapiEndpoint := "/rest/api/3/permissionscheme/10100" raw, err := os.ReadFile("../testing/mock-data/permissionscheme.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(writer http.ResponseWriter, request *http.Request) { testMethod(t, request, http.MethodGet) - testRequestURL(t, request, testAPIEdpoint) + testRequestURL(t, request, testapiEndpoint) fmt.Fprint(writer, string(raw)) }) @@ -86,14 +86,14 @@ func TestPermissionSchemeService_Get(t *testing.T) { func TestPermissionSchemeService_Get_NoScheme(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/3/permissionscheme/99999" + testapiEndpoint := "/rest/api/3/permissionscheme/99999" raw, err := os.ReadFile("../testing/mock-data/no_permissionscheme.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(writer http.ResponseWriter, request *http.Request) { testMethod(t, request, http.MethodGet) - testRequestURL(t, request, testAPIEdpoint) + testRequestURL(t, request, testapiEndpoint) fmt.Fprint(writer, string(raw)) }) diff --git a/onpremise/priority_test.go b/onpremise/priority_test.go index 91edd49..7008781 100644 --- a/onpremise/priority_test.go +++ b/onpremise/priority_test.go @@ -11,15 +11,15 @@ import ( func TestPriorityService_GetList(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/priority" + testapiEndpoint := "/rest/api/2/priority" raw, err := os.ReadFile("../testing/mock-data/all_priorities.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, string(raw)) }) diff --git a/onpremise/project_test.go b/onpremise/project_test.go index e997a07..9d17757 100644 --- a/onpremise/project_test.go +++ b/onpremise/project_test.go @@ -11,13 +11,13 @@ import ( func TestProjectService_GetAll(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/project" + testapiEndpoint := "/rest/api/2/project" raw, err := os.ReadFile("../testing/mock-data/all_projects.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) testRequestURL(t, r, "/rest/api/2/project?expand=issueTypes") fmt.Fprint(w, string(raw)) @@ -35,15 +35,15 @@ func TestProjectService_GetAll(t *testing.T) { func TestProjectService_Get(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/project/12310505" + testapiEndpoint := "/rest/api/2/project/12310505" raw, err := os.ReadFile("../testing/mock-data/project.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, string(raw)) }) @@ -63,11 +63,11 @@ func TestProjectService_Get(t *testing.T) { func TestProjectService_Get_NoProject(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/project/99999999" + testapiEndpoint := "/rest/api/2/project/99999999" - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, nil) }) @@ -87,11 +87,11 @@ func TestProjectService_Get_NoProject(t *testing.T) { func TestProjectService_GetPermissionScheme_Failure(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/project/99999999/permissionscheme" + testapiEndpoint := "/rest/api/2/project/99999999/permissionscheme" - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, nil) }) @@ -111,11 +111,11 @@ func TestProjectService_GetPermissionScheme_Failure(t *testing.T) { func TestProjectService_GetPermissionScheme_Success(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/project/99999999/permissionscheme" + testapiEndpoint := "/rest/api/2/project/99999999/permissionscheme" - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, `{ "expand": "permissions,user,group,projectRole,field,all", "id": 10201, diff --git a/onpremise/resolution_test.go b/onpremise/resolution_test.go index ce33192..378ec5b 100644 --- a/onpremise/resolution_test.go +++ b/onpremise/resolution_test.go @@ -11,15 +11,15 @@ import ( func TestResolutionService_GetList(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/resolution" + testapiEndpoint := "/rest/api/2/resolution" raw, err := os.ReadFile("../testing/mock-data/all_resolutions.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, string(raw)) }) diff --git a/onpremise/role_test.go b/onpremise/role_test.go index 8762bdd..f876035 100644 --- a/onpremise/role_test.go +++ b/onpremise/role_test.go @@ -64,14 +64,14 @@ func TestRoleService_GetList(t *testing.T) { func TestRoleService_Get_NoRole(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/3/role/99999" + testapiEndpoint := "/rest/api/3/role/99999" raw, err := os.ReadFile("../testing/mock-data/no_role.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(writer http.ResponseWriter, request *http.Request) { testMethod(t, request, http.MethodGet) - testRequestURL(t, request, testAPIEdpoint) + testRequestURL(t, request, testapiEndpoint) fmt.Fprint(writer, string(raw)) }) @@ -87,14 +87,14 @@ func TestRoleService_Get_NoRole(t *testing.T) { func TestRoleService_Get(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/3/role/10002" + testapiEndpoint := "/rest/api/3/role/10002" raw, err := os.ReadFile("../testing/mock-data/role.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(writer http.ResponseWriter, request *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(writer http.ResponseWriter, request *http.Request) { testMethod(t, request, http.MethodGet) - testRequestURL(t, request, testAPIEdpoint) + testRequestURL(t, request, testapiEndpoint) fmt.Fprint(writer, string(raw)) }) diff --git a/onpremise/sprint_test.go b/onpremise/sprint_test.go index 4b99575..cc0eaf2 100644 --- a/onpremise/sprint_test.go +++ b/onpremise/sprint_test.go @@ -43,15 +43,15 @@ func TestSprintService_MoveIssuesToSprint(t *testing.T) { func TestSprintService_GetIssuesForSprint(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/agile/1.0/sprint/123/issue" + testapiEndpoint := "/rest/agile/1.0/sprint/123/issue" raw, err := os.ReadFile("../testing/mock-data/issues_in_sprint.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, string(raw)) }) diff --git a/onpremise/status_test.go b/onpremise/status_test.go index 255d59b..9059490 100644 --- a/onpremise/status_test.go +++ b/onpremise/status_test.go @@ -11,16 +11,16 @@ import ( func TestStatusService_GetAllStatuses(t *testing.T) { setup() defer teardown() - testAPIEdpoint := "/rest/api/2/status" + testapiEndpoint := "/rest/api/2/status" raw, err := os.ReadFile("../testing/mock-data/all_statuses.json") if err != nil { t.Error(err.Error()) } - testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, testAPIEdpoint) + testRequestURL(t, r, testapiEndpoint) fmt.Fprint(w, string(raw)) }) From b835d6c28b6530dffac6d3d663d9a1d3d4d7e642 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 21:06:32 +0200 Subject: [PATCH 130/189] Update Changelog: Cloud/User: Renamed `User.GetSelf` to `User.GetCurrentUser` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a8deeb..ede1797 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -397,6 +397,7 @@ client, err := jira.NewClient("https://...", tp.Client()) * Cloud/Authentication: `BasicAuthTransport.Password` was renamed to `BasicAuthTransport.APIToken` * Cloud/Authentication: Removes `CookieAuthTransport` and `AuthenticationService`, because this type of auth is not supported by the Jira cloud offering * Cloud/Component: The type `CreateComponentOptions` was renamed to `ComponentCreateOptions` +* Cloud/User: Renamed `User.GetSelf` to `User.GetCurrentUser` ### Features From 5002af4e72169db4b700411b7fe5e16c4620b097 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 21:16:31 +0200 Subject: [PATCH 131/189] Onpremise/Auth: Update README and examples for Bearer Auth Kudos goes to @stxphcodes for his work in https://github.com/andygrunwald/go-jira/pull/469 --- README.md | 17 ++++++++++++--- onpremise/examples/bearerauth/main.go | 30 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 onpremise/examples/bearerauth/main.go diff --git a/README.md b/README.md index d11bfb5..f669301 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Latest stable release: [v1.16.0](https://github.com/andygrunwald/go-jira/release ## Features -* Authentication (HTTP Basic, OAuth, Session Cookie) +* Authentication (HTTP Basic, OAuth, Session Cookie, Bearer (for PATs)) * Create and retrieve issues * Create and retrieve issue transitions (status updates) * Call every API endpoint of the Jira, even if it is not directly implemented in this library @@ -92,7 +92,7 @@ func main() { ### Authentication The `go-jira` library does not handle most authentication directly. Instead, authentication should be handled within -an `http.Client`. That client can then be passed into the `NewClient` function when creating a jira client. +an `http.Client`. That client can then be passed into the `NewClient` function when creating a jira client. For convenience, capability for basic and cookie-based authentication is included in the main library. @@ -118,11 +118,20 @@ func main() { } ``` +#### Bearer - Personal Access Tokens (self-hosted Jira) + +For **self-hosted Jira** (v8.14 and later), Personal Access Tokens (PATs) were introduced. +Similar to the API tokens, PATs are a safe alternative to using username and password for authentication with scripts and integrations. +PATs use the Bearer authentication scheme. +Read more about Jira PATs [here](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html). + +See [examples/bearerauth](onpremise/examples/bearerauth/main.go) for how to use the Bearer authentication scheme with Jira in Go. + #### Basic (self-hosted Jira) Password-based API authentication works for self-hosted Jira **only**, and has been [deprecated for users of Atlassian Cloud](https://developer.atlassian.com/cloud/jira/platform/deprecation-notice-basic-auth-and-cookie-based-auth/). -The above token authentication example may be used, substituting a user's password for a generated token. +Depending on your version of Jira, either of the above token authentication examples may be used, substituting a user's password for a generated token. #### Authenticate with OAuth @@ -223,6 +232,7 @@ func main() { fmt.Printf("Status after transition: %+v\n", issue.Fields.Status.Name) } ``` + ### Get all the issues for JQL with Pagination Jira API has limit on maxResults it can return. You may have a usecase where you need to get all issues for given JQL. @@ -354,6 +364,7 @@ You can read more about them at https://blog.developer.atlassian.com/cloud-ecosy ## Releasing Install [standard-version](https://github.com/conventional-changelog/standard-version) + ```bash npm i -g standard-version ``` diff --git a/onpremise/examples/bearerauth/main.go b/onpremise/examples/bearerauth/main.go new file mode 100644 index 0000000..13054e3 --- /dev/null +++ b/onpremise/examples/bearerauth/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "fmt" + + jira "github.com/andygrunwald/go-jira/onpremise" +) + +func main() { + jiraURL := "" + + // See "Using Personal Access Tokens" + // https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html + tp := jira.BearerAuthTransport{ + Token: "", + } + client, err := jira.NewClient(jiraURL, tp.Client()) + if err != nil { + panic(err) + } + + u, _, err := client.User.GetCurrentUser(context.Background()) + if err != nil { + panic(err) + } + + fmt.Printf("Email: %v\n", u.EmailAddress) + fmt.Println("Success!") +} From 051e945560f716bcf5b155d1dc52c5749c3b3574 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 21:18:43 +0200 Subject: [PATCH 132/189] Onpremise/Example: Fix bearerauth example --- onpremise/examples/bearerauth/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onpremise/examples/bearerauth/main.go b/onpremise/examples/bearerauth/main.go index 13054e3..bb351de 100644 --- a/onpremise/examples/bearerauth/main.go +++ b/onpremise/examples/bearerauth/main.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - jira "github.com/andygrunwald/go-jira/onpremise" + jira "github.com/andygrunwald/go-jira/v2/onpremise" ) func main() { @@ -20,7 +20,7 @@ func main() { panic(err) } - u, _, err := client.User.GetCurrentUser(context.Background()) + u, _, err := client.User.GetSelf(context.Background()) if err != nil { panic(err) } From 9eaa723ed36972928467ac6417e9e95e4bb51e68 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 21:34:01 +0200 Subject: [PATCH 133/189] Cloud/Board: Add Location to Board struct Thanks goes to @wardviaene for his work in https://github.com/andygrunwald/go-jira/pull/455 --- cloud/board.go | 33 ++++++++++++++++++++----------- testing/mock-data/all_boards.json | 10 +++++++++- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/cloud/board.go b/cloud/board.go index 7ae4fd3..264779b 100644 --- a/cloud/board.go +++ b/cloud/board.go @@ -23,11 +23,24 @@ type BoardsList struct { // Board represents a Jira agile board type Board struct { - ID int `json:"id,omitempty" structs:"id,omitempty"` - Self string `json:"self,omitempty" structs:"self,omitempty"` - Name string `json:"name,omitempty" structs:"name,omitemtpy"` - Type string `json:"type,omitempty" structs:"type,omitempty"` - FilterID int `json:"filterId,omitempty" structs:"filterId,omitempty"` + ID int `json:"id,omitempty" structs:"id,omitempty"` + Self string `json:"self,omitempty" structs:"self,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitemtpy"` + Type string `json:"type,omitempty" structs:"type,omitempty"` + Location BoardLocation `json:"location,omitempty" structs:"location,omitempty"` + FilterID int `json:"filterId,omitempty" structs:"filterId,omitempty"` +} + +// BoardLocation represents the location of a Jira board +type BoardLocation struct { + ProjectID int `json:"projectId"` + UserID int `json:"userId"` + UserAccountID string `json:"userAccountId"` + DisplayName string `json:"displayName"` + ProjectName string `json:"projectName"` + ProjectKey string `json:"projectKey"` + ProjectTypeKey string `json:"projectTypeKey"` + Name string `json:"name"` } // BoardListOptions specifies the optional parameters to the BoardService.GetList @@ -152,14 +165,12 @@ func (s *BoardService) GetAllBoards(ctx context.Context, opt *BoardListOptions) return boards, resp, err } -// GetBoard will returns the board for the given boardID. +// GetBoard returns the board for the given board ID. // This board will only be returned if the user has permission to view it. +// Admins without the view permission will see the board as a private one, so will see only a subset of the board's data (board location for instance). // -// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard -// -// TODO Double check this method if this works as expected, is using the latest API and the response is complete -// This double check effort is done for v2 - Remove this two lines if this is completed. -func (s *BoardService) GetBoard(ctx context.Context, boardID int) (*Board, *Response, error) { +// Jira API docs: https://developer.atlassian.com/cloud/jira/software/rest/api-group-board/#api-rest-agile-1-0-board-boardid-get +func (s *BoardService) GetBoard(ctx context.Context, boardID int64) (*Board, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { diff --git a/testing/mock-data/all_boards.json b/testing/mock-data/all_boards.json index 25768ff..5e36776 100644 --- a/testing/mock-data/all_boards.json +++ b/testing/mock-data/all_boards.json @@ -38,7 +38,15 @@ "id": 1, "self": "https://test.jira.org/rest/agile/1.0/board/1", "name": "Test Mobile", - "type": "scrum" + "type": "scrum", + "location": { + "projectId": 10000, + "displayName": "aproject (AP)", + "projectName": "aproject", + "projectKey": "AP", + "projectTypeKey": "software", + "name": "aproject (AP)" + } } ] } \ No newline at end of file From c7a74104ccaec75d3a3016024fa6721abd7753be Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 21:48:46 +0200 Subject: [PATCH 134/189] Cloud/Board: Add Sprint goal Thanks goes to @Avarei for his work in https://github.com/andygrunwald/go-jira/pull/463 --- cloud/board.go | 6 ++---- testing/mock-data/sprints.json | 15 +++++++++++++++ testing/mock-data/sprints_filtered.json | 3 ++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/cloud/board.go b/cloud/board.go index 264779b..1505831 100644 --- a/cloud/board.go +++ b/cloud/board.go @@ -84,6 +84,7 @@ type Sprint struct { OriginBoardID int `json:"originBoardId" structs:"originBoardId"` Self string `json:"self" structs:"self"` State string `json:"state" structs:"state"` + Goal string `json:"goal,omitempty" structs:"goal"` } // BoardConfiguration represents a boardConfiguration of a jira board @@ -240,10 +241,7 @@ func (s *BoardService) DeleteBoard(ctx context.Context, boardID int) (*Board, *R // This only includes sprints that the user has permission to view. // // Jira API docs: https://developer.atlassian.com/cloud/jira/software/rest/api-group-board/#api-rest-agile-1-0-board-boardid-sprint-get -// -// TODO Double check this method if this works as expected, is using the latest API and the response is complete -// This double check effort is done for v2 - Remove this two lines if this is completed. -func (s *BoardService) GetAllSprints(ctx context.Context, boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { +func (s *BoardService) GetAllSprints(ctx context.Context, boardID int64, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/sprint", boardID) url, err := addOptions(apiEndpoint, options) if err != nil { diff --git a/testing/mock-data/sprints.json b/testing/mock-data/sprints.json index 508ca35..ad57cb8 100644 --- a/testing/mock-data/sprints.json +++ b/testing/mock-data/sprints.json @@ -41,6 +41,21 @@ "self": "https://jira.com/rest/agile/1.0/sprint/832", "startDate": "2016-06-20T13:24:39.161-07:00", "state": "active" + }, + { + "id": 845, + "self":"https://jira.com/rest/agile/1.0/sprint/845", + "state": "future", + "name": "Minimal Example Sprint", + "originBoardId": 734 + }, + { + "id": 846, + "self": "https://jira.com/rest/agile/1.0/sprint/846", + "state": "future", + "name": "Sprint with a Goal", + "originBoardId": 734, + "goal": "My Goal" } ] } diff --git a/testing/mock-data/sprints_filtered.json b/testing/mock-data/sprints_filtered.json index 29bdb23..5cb0a70 100644 --- a/testing/mock-data/sprints_filtered.json +++ b/testing/mock-data/sprints_filtered.json @@ -10,7 +10,8 @@ "originBoardId": 734, "self": "https://jira.com/rest/agile/1.0/sprint/832", "startDate": "2016-06-20T13:24:39.161-07:00", - "state": "active" + "state": "active", + "goal": "My Goal" } ] } From 589142389bddc54f7770066c87b7512c208b802e Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 19 Oct 2022 22:19:14 +0200 Subject: [PATCH 135/189] Cloud/Group: Update Add/Remove and replace username parameter with accountId Additionally: * Cloud/Group: Renamed `Group.Add` to `Group.AddUserByGroupName` * Cloud/Group: Renamed `Group.Remove` to `Group.RemoveUserByGroupName` Thanks a lot to @t-junjie for the work in https://github.com/andygrunwald/go-jira/pull/414 --- CHANGELOG.md | 2 ++ cloud/group.go | 52 ++++++++++++++++++++++----------------------- cloud/group_test.go | 12 +++++------ 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ede1797..d9e4e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -398,6 +398,8 @@ client, err := jira.NewClient("https://...", tp.Client()) * Cloud/Authentication: Removes `CookieAuthTransport` and `AuthenticationService`, because this type of auth is not supported by the Jira cloud offering * Cloud/Component: The type `CreateComponentOptions` was renamed to `ComponentCreateOptions` * Cloud/User: Renamed `User.GetSelf` to `User.GetCurrentUser` +* Cloud/Group: Renamed `Group.Add` to `Group.AddUserByGroupName` +* Cloud/Group: Renamed `Group.Remove` to `Group.RemoveUserByGroupName` ### Features diff --git a/cloud/group.go b/cloud/group.go index 0694b13..38736fa 100644 --- a/cloud/group.go +++ b/cloud/group.go @@ -23,19 +23,19 @@ type groupMembersResult struct { // Group represents a Jira group type Group struct { - ID string `json:"id"` - Title string `json:"title"` - Type string `json:"type"` - Properties groupProperties `json:"properties"` - AdditionalProperties bool `json:"additionalProperties"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + Self string `json:"self,omitempty" structs:"self,omitempty"` + Users GroupMembers `json:"users,omitempty" structs:"users,omitempty"` + Expand string `json:"expand,omitempty" structs:"expand,omitempty"` } -type groupProperties struct { - Name groupPropertiesName `json:"name"` -} - -type groupPropertiesName struct { - Type string `json:"type"` +// GroupMembers represent members in a Jira group +type GroupMembers struct { + Size int `json:"size,omitempty" structs:"size,omitempty"` + Items []GroupMember `json:"items,omitempty" structs:"items,omitempty"` + MaxResults int `json:"max-results,omitempty" structs:"max-results.omitempty"` + StartIndex int `json:"start-index,omitempty" structs:"start-index,omitempty"` + EndIndex int `json:"end-index,omitempty" structs:"end-index,omitempty"` } // GroupMember reflects a single member of a group @@ -95,18 +95,18 @@ func (s *GroupService) Get(ctx context.Context, name string, options *GroupSearc return group.Members, resp, nil } -// Add adds user to group +// Add adds a user to a group. // -// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-addUserToGroup +// The account ID of the user, which uniquely identifies the user across all Atlassian products. +// For example, 5b10ac8d82e05b22cc7d4ef5. // -// TODO Double check this method if this works as expected, is using the latest API and the response is complete -// This double check effort is done for v2 - Remove this two lines if this is completed. -func (s *GroupService) Add(ctx context.Context, groupname string, username string) (*Group, *Response, error) { - apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s", groupname) +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-groups/#api-rest-api-3-group-user-post +func (s *GroupService) AddUserByGroupName(ctx context.Context, groupName string, accountID string) (*Group, *Response, error) { + apiEndpoint := fmt.Sprintf("/rest/api/3/group/user?groupname=%s", groupName) var user struct { - Name string `json:"name"` + AccountID string `json:"accountId"` } - user.Name = username + user.AccountID = accountID req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, &user) if err != nil { return nil, nil, err @@ -122,15 +122,15 @@ func (s *GroupService) Add(ctx context.Context, groupname string, username strin return responseGroup, resp, nil } -// Remove removes user from group +// Remove removes a user from a group. // -// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-removeUserFromGroup -// Caller must close resp.Body +// The account ID of the user, which uniquely identifies the user across all Atlassian products. +// For example, 5b10ac8d82e05b22cc7d4ef5. // -// TODO Double check this method if this works as expected, is using the latest API and the response is complete -// This double check effort is done for v2 - Remove this two lines if this is completed. -func (s *GroupService) Remove(ctx context.Context, groupname string, username string) (*Response, error) { - apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s&username=%s", groupname, username) +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-groups/#api-rest-api-3-group-user-delete +// Caller must close resp.Body +func (s *GroupService) RemoveUserByGroupName(ctx context.Context, groupName string, accountID string) (*Response, error) { + apiEndpoint := fmt.Sprintf("/rest/api/3/group/user?groupname=%s&accountId=%s", groupName, accountID) req, err := s.client.NewRequest(ctx, http.MethodDelete, apiEndpoint, nil) if err != nil { return nil, err diff --git a/cloud/group_test.go b/cloud/group_test.go index 596ac61..e37f79e 100644 --- a/cloud/group_test.go +++ b/cloud/group_test.go @@ -65,15 +65,15 @@ func TestGroupService_GetPage(t *testing.T) { func TestGroupService_Add(t *testing.T) { setup() defer teardown() - testMux.HandleFunc("/rest/api/2/group/user", func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc("/rest/api/3/group/user", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodPost) - testRequestURL(t, r, "/rest/api/2/group/user?groupname=default") + testRequestURL(t, r, "/rest/api/3/group/user?groupname=default") w.WriteHeader(http.StatusCreated) fmt.Fprint(w, `{"name":"default","self":"http://www.example.com/jira/rest/api/2/group?groupname=default","users":{"size":1,"items":[],"max-results":50,"start-index":0,"end-index":0},"expand":"users"}`) }) - if group, _, err := testClient.Group.Add(context.Background(), "default", "theodore"); err != nil { + if group, _, err := testClient.Group.AddUserByGroupName(context.Background(), "default", "5b10ac8d82e05b22cc7d4ef5"); err != nil { t.Errorf("Error given: %s", err) } else if group == nil { t.Error("Expected group. Group is nil") @@ -83,15 +83,15 @@ func TestGroupService_Add(t *testing.T) { func TestGroupService_Remove(t *testing.T) { setup() defer teardown() - testMux.HandleFunc("/rest/api/2/group/user", func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc("/rest/api/3/group/user", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodDelete) - testRequestURL(t, r, "/rest/api/2/group/user?groupname=default") + testRequestURL(t, r, "/rest/api/3/group/user?groupname=default") w.WriteHeader(http.StatusOK) fmt.Fprint(w, `{"name":"default","self":"http://www.example.com/jira/rest/api/2/group?groupname=default","users":{"size":1,"items":[],"max-results":50,"start-index":0,"end-index":0},"expand":"users"}`) }) - if _, err := testClient.Group.Remove(context.Background(), "default", "theodore"); err != nil { + if _, err := testClient.Group.RemoveUserByGroupName(context.Background(), "default", "5b10ac8d82e05b22cc7d4ef5"); err != nil { t.Errorf("Error given: %s", err) } } From e7a6dfabdac4f6bcd2d87e0271b12fbfd6ec7d9b Mon Sep 17 00:00:00 2001 From: Vladimir Chalupecky Date: Sun, 23 Oct 2022 19:17:25 +0200 Subject: [PATCH 136/189] Cloud/Issue: add missing comments about closing Response.Body --- cloud/issue.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cloud/issue.go b/cloud/issue.go index c0ed078..9bb3ebf 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -1195,6 +1195,7 @@ func (s *IssueService) GetTransitions(ctx context.Context, id string) ([]Transit // When performing the transition you can update or set other issue fields. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition +// Caller must close Response.Body // // TODO Double check this method if this works as expected, is using the latest API and the response is complete // This double check effort is done for v2 - Remove this two lines if this is completed. @@ -1500,6 +1501,7 @@ func (s *IssueService) AddRemoteLink(ctx context.Context, issueID string, remote // UpdateRemoteLink updates a remote issue link by linkID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-remote-links/#api-rest-api-2-issue-issueidorkey-remotelink-linkid-put +// Caller must close Response.Body // // TODO Double check this method if this works as expected, is using the latest API and the response is complete // This double check effort is done for v2 - Remove this two lines if this is completed. From 9c3184e496f1d25c7cef9aec1da55988820d5056 Mon Sep 17 00:00:00 2001 From: Vladimir Chalupecky Date: Sun, 23 Oct 2022 19:20:41 +0200 Subject: [PATCH 137/189] Onpremise/Issue: add missing comments about closing Response.Body --- onpremise/issue.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/onpremise/issue.go b/onpremise/issue.go index c83402a..c933994 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -1195,6 +1195,7 @@ func (s *IssueService) GetTransitions(ctx context.Context, id string) ([]Transit // When performing the transition you can update or set other issue fields. // // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition +// Caller must close Response.Body // // TODO Double check this method if this works as expected, is using the latest API and the response is complete // This double check effort is done for v2 - Remove this two lines if this is completed. @@ -1500,6 +1501,7 @@ func (s *IssueService) AddRemoteLink(ctx context.Context, issueID string, remote // UpdateRemoteLink updates a remote issue link by linkID. // // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-remote-links/#api-rest-api-2-issue-issueidorkey-remotelink-linkid-put +// Caller must close Response.Body // // TODO Double check this method if this works as expected, is using the latest API and the response is complete // This double check effort is done for v2 - Remove this two lines if this is completed. From af9dc6835cb264f3d212d3d6b85722c82ec44cc9 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Tue, 22 Nov 2022 07:49:44 +0100 Subject: [PATCH 138/189] Issue Comments: Add field "properties" Docs: - Jira Cloud: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comments/#api-rest-api-3-issue-issueidorkey-comment-post - Jira Server: https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issue-addComment Fix #592 --- cloud/issue.go | 3 +++ onpremise/issue.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/cloud/issue.go b/cloud/issue.go index 9bb3ebf..e7db3c7 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -480,6 +480,9 @@ type Comment struct { Updated string `json:"updated,omitempty" structs:"updated,omitempty"` Created string `json:"created,omitempty" structs:"created,omitempty"` Visibility CommentVisibility `json:"visibility,omitempty" structs:"visibility,omitempty"` + + // A list of comment properties. Optional on create and update. + Properties []EntityProperty `json:"properties,omitempty" structs:"properties,omitempty"` } // FixVersion represents a software release in which an issue is fixed. diff --git a/onpremise/issue.go b/onpremise/issue.go index c933994..d1bca7a 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -480,6 +480,9 @@ type Comment struct { Updated string `json:"updated,omitempty" structs:"updated,omitempty"` Created string `json:"created,omitempty" structs:"created,omitempty"` Visibility CommentVisibility `json:"visibility,omitempty" structs:"visibility,omitempty"` + + // A list of comment properties. Optional on create and update. + Properties []EntityProperty `json:"properties,omitempty" structs:"properties,omitempty"` } // FixVersion represents a software release in which an issue is fixed. From dfa35654b0b6dc5e2cd9ec2d81a8f0ab935eb51a Mon Sep 17 00:00:00 2001 From: Roshan Raj Date: Wed, 23 Nov 2022 11:09:11 -0800 Subject: [PATCH 139/189] fix(code-snippet): example jira ticket status update var testIssueID was not declared but was being used across the example. --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f669301..04a9194 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,7 @@ import ( ) func main() { + testIssueID := "FART-1" base := "https://my.jira.com" tp := jira.BasicAuthTransport{ Username: "username", @@ -214,12 +215,12 @@ func main() { panic(err) } - issue, _, _ := jiraClient.Issue.Get("FART-1", nil) + issue, _, _ := jiraClient.Issue.Get(testIssueID, nil) currentStatus := issue.Fields.Status.Name fmt.Printf("Current status: %s\n", currentStatus) var transitionID string - possibleTransitions, _, _ := jiraClient.Issue.GetTransitions("FART-1") + possibleTransitions, _, _ := jiraClient.Issue.GetTransitions(testIssueID) for _, v := range possibleTransitions { if v.Name == "In Progress" { transitionID = v.ID @@ -227,7 +228,7 @@ func main() { } } - jiraClient.Issue.DoTransition("FART-1", transitionID) + jiraClient.Issue.DoTransition(testIssueID, transitionID) issue, _, _ = jiraClient.Issue.Get(testIssueID, nil) fmt.Printf("Status after transition: %+v\n", issue.Fields.Status.Name) } From 7b7e0c0d60a685a190c9f8636e41031193502aed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Jan 2023 02:07:20 +0000 Subject: [PATCH 140/189] chore(deps): bump dominikh/staticcheck-action from 1.2.0 to 1.3.0 Bumps [dominikh/staticcheck-action](https://github.com/dominikh/staticcheck-action) from 1.2.0 to 1.3.0. - [Release notes](https://github.com/dominikh/staticcheck-action/releases) - [Changelog](https://github.com/dominikh/staticcheck-action/blob/master/CHANGES.md) - [Commits](https://github.com/dominikh/staticcheck-action/compare/v1.2.0...v1.3.0) --- updated-dependencies: - dependency-name: dominikh/staticcheck-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 81e1672..cdd4909 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -66,7 +66,7 @@ jobs: go-version: 1.19 - name: Run staticcheck (Go ${{ matrix.go }}) - uses: dominikh/staticcheck-action@v1.2.0 + uses: dominikh/staticcheck-action@v1.3.0 with: version: "2022.1" install-go: false From 081901eae165fd6f6bf627ff0b80a1fac1fc19a2 Mon Sep 17 00:00:00 2001 From: Kristof Daja Date: Tue, 14 Feb 2023 14:03:37 +0100 Subject: [PATCH 141/189] Update README.md corrected the signature of jira.NewClient() in the PAT example. first comes the httpClient jira.httpClient and second comes the baseURL string --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 04a9194..5f09155 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ func main() { APIToken: "", } - client, err := jira.NewClient("https://my.jira.com", tp.Client()) + client, err := jira.NewClient(tp.Client(), "https://my.jira.com") u, _, err = client.User.GetCurrentUser(context.Background()) From bdbfed6ac5a42fc62c7c522cf8e5054f51862ebc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Feb 2023 03:19:43 +0000 Subject: [PATCH 142/189] chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.4.2 to 4.5.0 Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.4.2 to 4.5.0. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v4.4.2...v4.5.0) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v4 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b76d7e6..721c974 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/fatih/structs v1.1.0 - github.com/golang-jwt/jwt/v4 v4.4.2 + github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/go-cmp v0.5.9 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 diff --git a/go.sum b/go.sum index 6dcc5c0..9e2326b 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= -github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= From d9a8f8aa707f5f373c347e68fef1187bf930f9cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 02:58:07 +0000 Subject: [PATCH 143/189] chore(deps): bump golang.org/x/term Bumps [golang.org/x/term](https://github.com/golang/term) from 0.0.0-20210220032956-6a3ed077a48d to 0.6.0. - [Release notes](https://github.com/golang/term/releases) - [Commits](https://github.com/golang/term/commits/v0.6.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index b76d7e6..9cd6ab8 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 - golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d + golang.org/x/term v0.6.0 ) -require golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f // indirect +require golang.org/x/sys v0.6.0 // indirect diff --git a/go.sum b/go.sum index 6dcc5c0..4f2dc8e 100644 --- a/go.sum +++ b/go.sum @@ -9,9 +9,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY= -golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 2e11dffbdb9a4710fdc9233e8efe0a124e204e24 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 25 Mar 2023 09:01:57 +0100 Subject: [PATCH 144/189] Dependabot: Switch update interval from daily to monthly --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 83f6232..d433f2d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,9 +6,9 @@ updates: - package-ecosystem: "gomod" directory: "/" schedule: - interval: "daily" + interval: "monthly" - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" \ No newline at end of file + interval: "monthly" From 5716f96f09ceb0feadf362381bda09a3f73f95d5 Mon Sep 17 00:00:00 2001 From: Alex Vulaj Date: Fri, 9 Jun 2023 09:18:20 -0400 Subject: [PATCH 145/189] export custom types in user service that are exposed in function signatures --- README.md | 2 +- cloud/user.go | 52 +++++++++++++++++++++++++-------------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 5f09155..aa6ce79 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ **v2 of this library is in development.** **v2 will contain breaking changes :warning:** -**The current main branch can contains the development version of v2.** +**The current main branch contains the development version of v2.** The goals of v2 are: diff --git a/cloud/user.go b/cloud/user.go index 3453a72..1a8fea8 100644 --- a/cloud/user.go +++ b/cloud/user.go @@ -67,14 +67,14 @@ type ApplicationRole struct { // Key `defaultGroupsDetails` missing - https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-application-roles/#api-rest-api-3-applicationrole-key-get } -type userSearchParam struct { +type UserSearchParam struct { name string value string } -type userSearch []userSearchParam +type UserSearch []UserSearchParam -type userSearchF func(userSearch) userSearch +type UserSearchF func(UserSearch) UserSearch // Get gets user info from Jira using its Account Id // @@ -210,57 +210,57 @@ func (s *UserService) GetCurrentUser(ctx context.Context) (*User, *Response, err } // WithMaxResults sets the max results to return -func WithMaxResults(maxResults int) userSearchF { - return func(s userSearch) userSearch { - s = append(s, userSearchParam{name: "maxResults", value: fmt.Sprintf("%d", maxResults)}) +func WithMaxResults(maxResults int) UserSearchF { + return func(s UserSearch) UserSearch { + s = append(s, UserSearchParam{name: "maxResults", value: fmt.Sprintf("%d", maxResults)}) return s } } // WithStartAt set the start pager -func WithStartAt(startAt int) userSearchF { - return func(s userSearch) userSearch { - s = append(s, userSearchParam{name: "startAt", value: fmt.Sprintf("%d", startAt)}) +func WithStartAt(startAt int) UserSearchF { + return func(s UserSearch) UserSearch { + s = append(s, UserSearchParam{name: "startAt", value: fmt.Sprintf("%d", startAt)}) return s } } // WithActive sets the active users lookup -func WithActive(active bool) userSearchF { - return func(s userSearch) userSearch { - s = append(s, userSearchParam{name: "includeActive", value: fmt.Sprintf("%t", active)}) +func WithActive(active bool) UserSearchF { + return func(s UserSearch) UserSearch { + s = append(s, UserSearchParam{name: "includeActive", value: fmt.Sprintf("%t", active)}) return s } } // WithInactive sets the inactive users lookup -func WithInactive(inactive bool) userSearchF { - return func(s userSearch) userSearch { - s = append(s, userSearchParam{name: "includeInactive", value: fmt.Sprintf("%t", inactive)}) +func WithInactive(inactive bool) UserSearchF { + return func(s UserSearch) UserSearch { + s = append(s, UserSearchParam{name: "includeInactive", value: fmt.Sprintf("%t", inactive)}) return s } } // WithUsername sets the username to search -func WithUsername(username string) userSearchF { - return func(s userSearch) userSearch { - s = append(s, userSearchParam{name: "username", value: username}) +func WithUsername(username string) UserSearchF { + return func(s UserSearch) UserSearch { + s = append(s, UserSearchParam{name: "username", value: username}) return s } } // WithAccountId sets the account id to search -func WithAccountId(accountId string) userSearchF { - return func(s userSearch) userSearch { - s = append(s, userSearchParam{name: "accountId", value: accountId}) +func WithAccountId(accountId string) UserSearchF { + return func(s UserSearch) UserSearch { + s = append(s, UserSearchParam{name: "accountId", value: accountId}) return s } } // WithProperty sets the property (Property keys are specified by path) to search -func WithProperty(property string) userSearchF { - return func(s userSearch) userSearch { - s = append(s, userSearchParam{name: "property", value: property}) +func WithProperty(property string) UserSearchF { + return func(s UserSearch) UserSearch { + s = append(s, UserSearchParam{name: "property", value: property}) return s } } @@ -272,8 +272,8 @@ func WithProperty(property string) userSearchF { // // TODO Double check this method if this works as expected, is using the latest API and the response is complete // This double check effort is done for v2 - Remove this two lines if this is completed. -func (s *UserService) Find(ctx context.Context, property string, tweaks ...userSearchF) ([]User, *Response, error) { - search := []userSearchParam{ +func (s *UserService) Find(ctx context.Context, property string, tweaks ...UserSearchF) ([]User, *Response, error) { + search := []UserSearchParam{ { name: "query", value: property, From 54fd60d419950880bf582b7372ae874744d4aa91 Mon Sep 17 00:00:00 2001 From: Dave Young Date: Mon, 4 Dec 2023 13:17:54 -0600 Subject: [PATCH 146/189] feat(issue): add statuscategorychangedate --- cloud/issue.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud/issue.go b/cloud/issue.go index e7db3c7..56d868e 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -121,6 +121,7 @@ type IssueFields struct { Reporter *User `json:"reporter,omitempty" structs:"reporter,omitempty"` Components []*Component `json:"components,omitempty" structs:"components,omitempty"` Status *Status `json:"status,omitempty" structs:"status,omitempty"` + StatusCategoryChangeDate Time `json:"statuscategorychangedate,omitempty" structs:"statuscategorychangedate,omitempty"` Progress *Progress `json:"progress,omitempty" structs:"progress,omitempty"` AggregateProgress *Progress `json:"aggregateprogress,omitempty" structs:"aggregateprogress,omitempty"` TimeTracking *TimeTracking `json:"timetracking,omitempty" structs:"timetracking,omitempty"` From 5605546dc9c21409ce4585e6d6ff57c9dad9d2d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:35:11 +0000 Subject: [PATCH 147/189] chore(deps): bump github.com/google/go-cmp from 0.5.9 to 0.6.0 Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.9 to 0.6.0. - [Release notes](https://github.com/google/go-cmp/releases) - [Commits](https://github.com/google/go-cmp/compare/v0.5.9...v0.6.0) --- updated-dependencies: - dependency-name: github.com/google/go-cmp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6f6b693..a242cd4 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/fatih/structs v1.1.0 github.com/golang-jwt/jwt/v4 v4.5.0 - github.com/google/go-cmp v0.5.9 + github.com/google/go-cmp v0.6.0 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 golang.org/x/term v0.6.0 diff --git a/go.sum b/go.sum index 074c0df..f01c5f0 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM= From 664d73463675a84041ad279dfb5bed54d21f5caa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:35:16 +0000 Subject: [PATCH 148/189] chore(deps): bump golang.org/x/term from 0.6.0 to 0.19.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.6.0 to 0.19.0. - [Commits](https://github.com/golang/term/compare/v0.6.0...v0.19.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 6f6b693..16d7ca0 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 - golang.org/x/term v0.6.0 + golang.org/x/term v0.19.0 ) -require golang.org/x/sys v0.6.0 // indirect +require golang.org/x/sys v0.19.0 // indirect diff --git a/go.sum b/go.sum index 074c0df..1015c90 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 8b4e5aa9dfaa2c37669d0a04b7347f2a6a07f431 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:35:39 +0000 Subject: [PATCH 149/189] chore(deps): bump actions/setup-go from 3 to 5 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v3...v5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/testing.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index cdd4909..dfa064b 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} @@ -34,7 +34,7 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v5 with: go-version: 1.19 @@ -48,7 +48,7 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v5 with: go-version: 1.19 @@ -61,7 +61,7 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v5 with: go-version: 1.19 From 2f2e9c9d31efdc7d3a0de273069bee6c0b416718 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:35:45 +0000 Subject: [PATCH 150/189] chore(deps): bump actions/labeler from 4 to 5 Bumps [actions/labeler](https://github.com/actions/labeler) from 4 to 5. - [Release notes](https://github.com/actions/labeler/releases) - [Commits](https://github.com/actions/labeler/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/labeler dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/label.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml index b37fcb9..f07b674 100644 --- a/.github/workflows/label.yml +++ b/.github/workflows/label.yml @@ -14,6 +14,6 @@ jobs: runs-on: ubuntu-latest if: (github.actor != 'dependabot[bot]') steps: - - uses: actions/labeler@v4 + - uses: actions/labeler@v5 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file From e3df22de2c1c24ad8a8ce3ffd05f136ab1cd7caf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:35:53 +0000 Subject: [PATCH 151/189] chore(deps): bump dominikh/staticcheck-action from 1.3.0 to 1.3.1 Bumps [dominikh/staticcheck-action](https://github.com/dominikh/staticcheck-action) from 1.3.0 to 1.3.1. - [Release notes](https://github.com/dominikh/staticcheck-action/releases) - [Changelog](https://github.com/dominikh/staticcheck-action/blob/master/CHANGES.md) - [Commits](https://github.com/dominikh/staticcheck-action/compare/v1.3.0...v1.3.1) --- updated-dependencies: - dependency-name: dominikh/staticcheck-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index cdd4909..4682759 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -66,7 +66,7 @@ jobs: go-version: 1.19 - name: Run staticcheck (Go ${{ matrix.go }}) - uses: dominikh/staticcheck-action@v1.3.0 + uses: dominikh/staticcheck-action@v1.3.1 with: version: "2022.1" install-go: false From 9f0609227fa516822a5a2783d38fc549896b6abf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:36:05 +0000 Subject: [PATCH 152/189] chore(deps): bump actions/setup-python from 4 to 5 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 7de518c..9a698a1 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: 3.x - run: pip install mkdocs-material From 5fb0a923b85c72959921c2c813a6e53ba951d8f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:42:44 +0000 Subject: [PATCH 153/189] chore(deps): bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/documentation.yml | 2 +- .github/workflows/testing.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 9a698a1..161807d 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -8,7 +8,7 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.x diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 6dc4aa6..df1fd14 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -20,7 +20,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: 1.19 @@ -47,7 +47,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: 1.19 @@ -60,7 +60,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: 1.19 From 3c7558a87246fe52a673c094fdcaa8adb63d5476 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 02:15:42 +0000 Subject: [PATCH 154/189] chore(deps): bump golang.org/x/term from 0.19.0 to 0.20.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.19.0 to 0.20.0. - [Commits](https://github.com/golang/term/compare/v0.19.0...v0.20.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 07c2542..9122582 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 - golang.org/x/term v0.19.0 + golang.org/x/term v0.20.0 ) -require golang.org/x/sys v0.19.0 // indirect +require golang.org/x/sys v0.20.0 // indirect diff --git a/go.sum b/go.sum index db63637..0444b37 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 58f453cb5f287ca1deb903f64051f12ecc031813 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 02:24:14 +0000 Subject: [PATCH 155/189] chore(deps): bump golang.org/x/term from 0.20.0 to 0.21.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.20.0 to 0.21.0. - [Commits](https://github.com/golang/term/compare/v0.20.0...v0.21.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 9122582..9c3707d 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 - golang.org/x/term v0.20.0 + golang.org/x/term v0.21.0 ) -require golang.org/x/sys v0.20.0 // indirect +require golang.org/x/sys v0.21.0 // indirect diff --git a/go.sum b/go.sum index 0444b37..1ded959 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 4cdf785314fd6b5425cce2105c168cd4b3a203be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Jul 2024 05:48:20 +0000 Subject: [PATCH 156/189] chore(deps): bump golang.org/x/term from 0.21.0 to 0.22.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.21.0 to 0.22.0. - [Commits](https://github.com/golang/term/compare/v0.21.0...v0.22.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 9c3707d..d373f67 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 - golang.org/x/term v0.21.0 + golang.org/x/term v0.22.0 ) -require golang.org/x/sys v0.21.0 // indirect +require golang.org/x/sys v0.22.0 // indirect diff --git a/go.sum b/go.sum index 1ded959..886082b 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 6543393c37c97ef59f2cadf368350fdfd1519dfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 06:08:49 +0000 Subject: [PATCH 157/189] chore(deps): bump golang.org/x/term from 0.22.0 to 0.23.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.22.0 to 0.23.0. - [Commits](https://github.com/golang/term/compare/v0.22.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index d373f67..5a305a6 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 - golang.org/x/term v0.22.0 + golang.org/x/term v0.23.0 ) -require golang.org/x/sys v0.22.0 // indirect +require golang.org/x/sys v0.23.0 // indirect diff --git a/go.sum b/go.sum index 886082b..62f60d3 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 9e8caa147fff88de6c68e737165b326b0a859606 Mon Sep 17 00:00:00 2001 From: BG Date: Mon, 30 Sep 2024 15:52:00 +0200 Subject: [PATCH 158/189] Switch to v2 attachment/content --- cloud/issue.go | 2 +- cloud/issue_test.go | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cloud/issue.go b/cloud/issue.go index 56d868e..91e6507 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -653,7 +653,7 @@ func (s *IssueService) Get(ctx context.Context, issueID string, options *GetQuer // TODO Double check this method if this works as expected, is using the latest API and the response is complete // This double check effort is done for v2 - Remove this two lines if this is completed. func (s *IssueService) DownloadAttachment(ctx context.Context, attachmentID string) (*Response, error) { - apiEndpoint := fmt.Sprintf("secure/attachment/%s/", attachmentID) + apiEndpoint := fmt.Sprintf("rest/api/2/attachment/content/%s/", attachmentID) req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, err diff --git a/cloud/issue_test.go b/cloud/issue_test.go index a8ad648..630aed7 100644 --- a/cloud/issue_test.go +++ b/cloud/issue_test.go @@ -401,9 +401,9 @@ func TestIssueService_DownloadAttachment(t *testing.T) { setup() defer teardown() - testMux.HandleFunc("/secure/attachment/", func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc("/rest/api/2/attachment/content/", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, "/secure/attachment/10000/") + testRequestURL(t, r, "/rest/api/2/attachment/content/10000/") w.WriteHeader(http.StatusOK) w.Write([]byte(testAttachment)) @@ -436,9 +436,9 @@ func TestIssueService_DownloadAttachment_BadStatus(t *testing.T) { setup() defer teardown() - testMux.HandleFunc("/secure/attachment/", func(w http.ResponseWriter, r *http.Request) { + testMux.HandleFunc("/rest/api/2/attachment/content/", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - testRequestURL(t, r, "/secure/attachment/10000/") + testRequestURL(t, r, "/rest/api/2/attachment/content/10000/") w.WriteHeader(http.StatusForbidden) }) @@ -487,7 +487,7 @@ func TestIssueService_PostAttachment(t *testing.T) { } } w.WriteHeader(status) - fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) + fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/rest/api/2/attachment/content/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) }) reader := strings.NewReader(testAttachment) @@ -535,7 +535,7 @@ func TestIssueService_PostAttachment_NoFilename(t *testing.T) { testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") w.WriteHeader(http.StatusOK) - fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) + fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/rest/api/2/attachment/content/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) }) reader := strings.NewReader(testAttachment) @@ -553,7 +553,7 @@ func TestIssueService_PostAttachment_NoAttachment(t *testing.T) { testMethod(t, r, http.MethodPost) testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") w.WriteHeader(http.StatusOK) - fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) + fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/rest/api/2/attachment/content/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) }) _, _, err := testClient.Issue.PostAttachment(context.Background(), "10000", nil, "attachment") From 6c72429b0bde21d3f31013598a19c5b168039532 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 02:22:01 +0000 Subject: [PATCH 159/189] chore(deps): bump golang.org/x/term from 0.23.0 to 0.24.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.23.0 to 0.24.0. - [Commits](https://github.com/golang/term/compare/v0.23.0...v0.24.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 5a305a6..6f350c1 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 - golang.org/x/term v0.23.0 + golang.org/x/term v0.24.0 ) -require golang.org/x/sys v0.23.0 // indirect +require golang.org/x/sys v0.25.0 // indirect diff --git a/go.sum b/go.sum index 62f60d3..235b3a5 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 68b6e972741656b8072c537b354d77faf3ad84a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 02:26:10 +0000 Subject: [PATCH 160/189] chore(deps): bump golang.org/x/term from 0.24.0 to 0.25.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.24.0 to 0.25.0. - [Commits](https://github.com/golang/term/compare/v0.24.0...v0.25.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 6f350c1..6818229 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 - golang.org/x/term v0.24.0 + golang.org/x/term v0.25.0 ) -require golang.org/x/sys v0.25.0 // indirect +require golang.org/x/sys v0.26.0 // indirect diff --git a/go.sum b/go.sum index 235b3a5..7cc5568 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 88e0c91883abfcec12a47d42c19a3c99b823567f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 23:44:20 +0000 Subject: [PATCH 161/189] chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.0 to 4.5.1. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.0...v4.5.1) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6818229..b275277 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/fatih/structs v1.1.0 - github.com/golang-jwt/jwt/v4 v4.5.0 + github.com/golang-jwt/jwt/v4 v4.5.1 github.com/google/go-cmp v0.6.0 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 diff --git a/go.sum b/go.sum index 7cc5568..581ed6e 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= From 253e65690f74921e5cc2804098ee6cc8e41b2741 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Dec 2024 08:40:27 +0000 Subject: [PATCH 162/189] chore(deps): bump golang.org/x/term from 0.25.0 to 0.27.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.25.0 to 0.27.0. - [Commits](https://github.com/golang/term/compare/v0.25.0...v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index b275277..89caf0e 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 - golang.org/x/term v0.25.0 + golang.org/x/term v0.27.0 ) -require golang.org/x/sys v0.26.0 // indirect +require golang.org/x/sys v0.28.0 // indirect diff --git a/go.sum b/go.sum index 581ed6e..5fbb2ac 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 75d2ddc71efc410c1cc3699ff1af6e308672a435 Mon Sep 17 00:00:00 2001 From: Oscar Miniet Date: Mon, 30 Dec 2024 15:36:13 -0500 Subject: [PATCH 163/189] fix: update comment fields of struct type to be pointers to fix json marshal for addcomment method on issue service --- cloud/issue.go | 18 +++++++++--------- cloud/issue_test.go | 6 +++--- onpremise/issue.go | 18 +++++++++--------- onpremise/issue_test.go | 6 +++--- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/cloud/issue.go b/cloud/issue.go index 91e6507..098c475 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -472,15 +472,15 @@ type Comments struct { // Comment represents a comment by a person to an issue in Jira. type Comment struct { - ID string `json:"id,omitempty" structs:"id,omitempty"` - Self string `json:"self,omitempty" structs:"self,omitempty"` - Name string `json:"name,omitempty" structs:"name,omitempty"` - Author User `json:"author,omitempty" structs:"author,omitempty"` - Body string `json:"body,omitempty" structs:"body,omitempty"` - UpdateAuthor User `json:"updateAuthor,omitempty" structs:"updateAuthor,omitempty"` - Updated string `json:"updated,omitempty" structs:"updated,omitempty"` - Created string `json:"created,omitempty" structs:"created,omitempty"` - Visibility CommentVisibility `json:"visibility,omitempty" structs:"visibility,omitempty"` + ID string `json:"id,omitempty" structs:"id,omitempty"` + Self string `json:"self,omitempty" structs:"self,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + Author *User `json:"author,omitempty" structs:"author,omitempty"` + Body string `json:"body,omitempty" structs:"body,omitempty"` + UpdateAuthor *User `json:"updateAuthor,omitempty" structs:"updateAuthor,omitempty"` + Updated string `json:"updated,omitempty" structs:"updated,omitempty"` + Created string `json:"created,omitempty" structs:"created,omitempty"` + Visibility *CommentVisibility `json:"visibility,omitempty" structs:"visibility,omitempty"` // A list of comment properties. Optional on create and update. Properties []EntityProperty `json:"properties,omitempty" structs:"properties,omitempty"` diff --git a/cloud/issue_test.go b/cloud/issue_test.go index 630aed7..09d8357 100644 --- a/cloud/issue_test.go +++ b/cloud/issue_test.go @@ -191,7 +191,7 @@ func TestIssueService_AddComment(t *testing.T) { c := &Comment{ Body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.", - Visibility: CommentVisibility{ + Visibility: &CommentVisibility{ Type: "role", Value: "Administrators", }, @@ -219,7 +219,7 @@ func TestIssueService_UpdateComment(t *testing.T) { c := &Comment{ ID: "10001", Body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.", - Visibility: CommentVisibility{ + Visibility: &CommentVisibility{ Type: "role", Value: "Administrators", }, @@ -316,7 +316,7 @@ func TestIssueService_AddLink(t *testing.T) { }, Comment: &Comment{ Body: "Linked related issue!", - Visibility: CommentVisibility{ + Visibility: &CommentVisibility{ Type: "group", Value: "jira-software-users", }, diff --git a/onpremise/issue.go b/onpremise/issue.go index d1bca7a..084fa98 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -471,15 +471,15 @@ type Comments struct { // Comment represents a comment by a person to an issue in Jira. type Comment struct { - ID string `json:"id,omitempty" structs:"id,omitempty"` - Self string `json:"self,omitempty" structs:"self,omitempty"` - Name string `json:"name,omitempty" structs:"name,omitempty"` - Author User `json:"author,omitempty" structs:"author,omitempty"` - Body string `json:"body,omitempty" structs:"body,omitempty"` - UpdateAuthor User `json:"updateAuthor,omitempty" structs:"updateAuthor,omitempty"` - Updated string `json:"updated,omitempty" structs:"updated,omitempty"` - Created string `json:"created,omitempty" structs:"created,omitempty"` - Visibility CommentVisibility `json:"visibility,omitempty" structs:"visibility,omitempty"` + ID string `json:"id,omitempty" structs:"id,omitempty"` + Self string `json:"self,omitempty" structs:"self,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` + Author *User `json:"author,omitempty" structs:"author,omitempty"` + Body string `json:"body,omitempty" structs:"body,omitempty"` + UpdateAuthor *User `json:"updateAuthor,omitempty" structs:"updateAuthor,omitempty"` + Updated string `json:"updated,omitempty" structs:"updated,omitempty"` + Created string `json:"created,omitempty" structs:"created,omitempty"` + Visibility *CommentVisibility `json:"visibility,omitempty" structs:"visibility,omitempty"` // A list of comment properties. Optional on create and update. Properties []EntityProperty `json:"properties,omitempty" structs:"properties,omitempty"` diff --git a/onpremise/issue_test.go b/onpremise/issue_test.go index 36b6aa3..d6f033c 100644 --- a/onpremise/issue_test.go +++ b/onpremise/issue_test.go @@ -191,7 +191,7 @@ func TestIssueService_AddComment(t *testing.T) { c := &Comment{ Body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.", - Visibility: CommentVisibility{ + Visibility: &CommentVisibility{ Type: "role", Value: "Administrators", }, @@ -219,7 +219,7 @@ func TestIssueService_UpdateComment(t *testing.T) { c := &Comment{ ID: "10001", Body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.", - Visibility: CommentVisibility{ + Visibility: &CommentVisibility{ Type: "role", Value: "Administrators", }, @@ -316,7 +316,7 @@ func TestIssueService_AddLink(t *testing.T) { }, Comment: &Comment{ Body: "Linked related issue!", - Visibility: CommentVisibility{ + Visibility: &CommentVisibility{ Type: "group", Value: "jira-software-users", }, From 8c053659001d227314c806cf7c0f067addcd184a Mon Sep 17 00:00:00 2001 From: Tore Martin Hagen Date: Fri, 24 Jan 2025 10:27:21 +0100 Subject: [PATCH 164/189] Fixed issue field cratro json tag #645 --- cloud/issue.go | 2 +- onpremise/issue.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud/issue.go b/cloud/issue.go index 91e6507..524d291 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -117,7 +117,7 @@ type IssueFields struct { Updated Time `json:"updated,omitempty" structs:"updated,omitempty"` Description string `json:"description,omitempty" structs:"description,omitempty"` Summary string `json:"summary,omitempty" structs:"summary,omitempty"` - Creator *User `json:"Creator,omitempty" structs:"Creator,omitempty"` + Creator *User `json:"creator,omitempty" structs:"creator,omitempty"` Reporter *User `json:"reporter,omitempty" structs:"reporter,omitempty"` Components []*Component `json:"components,omitempty" structs:"components,omitempty"` Status *Status `json:"status,omitempty" structs:"status,omitempty"` diff --git a/onpremise/issue.go b/onpremise/issue.go index d1bca7a..9a467f4 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -117,7 +117,7 @@ type IssueFields struct { Updated Time `json:"updated,omitempty" structs:"updated,omitempty"` Description string `json:"description,omitempty" structs:"description,omitempty"` Summary string `json:"summary,omitempty" structs:"summary,omitempty"` - Creator *User `json:"Creator,omitempty" structs:"Creator,omitempty"` + Creator *User `json:"creator,omitempty" structs:"creator,omitempty"` Reporter *User `json:"reporter,omitempty" structs:"reporter,omitempty"` Components []*Component `json:"components,omitempty" structs:"components,omitempty"` Status *Status `json:"status,omitempty" structs:"status,omitempty"` From 5f1007eba6f2290d04e300457d33fd4a2a350ddc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Feb 2025 02:57:34 +0000 Subject: [PATCH 165/189] chore(deps): bump golang.org/x/term from 0.27.0 to 0.28.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.27.0 to 0.28.0. - [Commits](https://github.com/golang/term/compare/v0.27.0...v0.28.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 89caf0e..31349cd 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 - golang.org/x/term v0.27.0 + golang.org/x/term v0.28.0 ) -require golang.org/x/sys v0.28.0 // indirect +require golang.org/x/sys v0.29.0 // indirect diff --git a/go.sum b/go.sum index 5fbb2ac..906f80d 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From be5ab4e10496014df2534269bb9d49f13bedc330 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Feb 2025 07:15:31 +0000 Subject: [PATCH 166/189] chore(deps): bump golang.org/x/term from 0.28.0 to 0.29.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.28.0 to 0.29.0. - [Commits](https://github.com/golang/term/compare/v0.28.0...v0.29.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 31349cd..fae2de3 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 - golang.org/x/term v0.28.0 + golang.org/x/term v0.29.0 ) -require golang.org/x/sys v0.29.0 // indirect +require golang.org/x/sys v0.30.0 // indirect diff --git a/go.sum b/go.sum index 906f80d..5a738a2 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 1068c2f44810f0dc8d6ddbb1900ec70700aa80ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Mar 2025 02:39:05 +0000 Subject: [PATCH 167/189] chore(deps): bump github.com/google/go-cmp from 0.6.0 to 0.7.0 Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.6.0 to 0.7.0. - [Release notes](https://github.com/google/go-cmp/releases) - [Commits](https://github.com/google/go-cmp/compare/v0.6.0...v0.7.0) --- updated-dependencies: - dependency-name: github.com/google/go-cmp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fae2de3..fe8a80e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/fatih/structs v1.1.0 github.com/golang-jwt/jwt/v4 v4.5.1 - github.com/google/go-cmp v0.6.0 + github.com/google/go-cmp v0.7.0 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 golang.org/x/term v0.29.0 diff --git a/go.sum b/go.sum index 5a738a2..0e9ed30 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM= From 1801ec81c5bc02999677f6b481258a134b09d1fe Mon Sep 17 00:00:00 2001 From: Reece Russell Date: Sun, 16 Mar 2025 17:37:33 +0000 Subject: [PATCH 168/189] fix: update example to use correct property name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa6ce79..a8a1d43 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ A more thorough, [runnable example](cloud/examples/basic_auth/main.go) is provid func main() { tp := jira.BasicAuthTransport{ Username: "", - APIToken: "", + Password: "", } client, err := jira.NewClient(tp.Client(), "https://my.jira.com") From dc7f9f3eb4e8cb40f8717ec18f12b9abe83e88e9 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 13:31:33 +0100 Subject: [PATCH 169/189] CI: Update Go versions from '1.19', '1.18' to '1.23', '1.24' and fix matrix usage --- .github/workflows/testing.yml | 38 ++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index df1fd14..f041a9e 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - go: [ '1.19', '1.18' ] + go: [ '1.23', '1.24' ] os: [ 'windows-latest', 'ubuntu-latest', 'macOS-latest' ] runs-on: ${{ matrix.os }} @@ -30,13 +30,19 @@ jobs: fmt: name: go fmt - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + go-version: [ '1.23', '1.24' ] + os: [ 'windows-latest', 'ubuntu-latest', 'macOS-latest' ] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - name: Setup Go ${{ matrix.go-version }} + uses: actions/setup-go@v5 with: - go-version: 1.19 + go-version: ${{ matrix.go-version }} - name: Run go fmt (Go ${{ matrix.go }}) if: runner.os != 'Windows' @@ -44,26 +50,38 @@ jobs: vet: name: go vet - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + go-version: [ '1.23', '1.24' ] + os: [ 'windows-latest', 'ubuntu-latest', 'macOS-latest' ] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - name: Setup Go ${{ matrix.go-version }} + uses: actions/setup-go@v5 with: - go-version: 1.19 + go-version: ${{ matrix.go-version }} - name: Run go vet run: make vet staticcheck: name: staticcheck - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + go-version: [ '1.23', '1.24' ] + os: [ 'windows-latest', 'ubuntu-latest', 'macOS-latest' ] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - name: Setup Go ${{ matrix.go-version }} + uses: actions/setup-go@v5 with: - go-version: 1.19 + go-version: ${{ matrix.go-version }} - name: Run staticcheck (Go ${{ matrix.go }}) uses: dominikh/staticcheck-action@v1.3.1 From 21b7be49e738361fe7cff0cd099465cf060d41ea Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 13:33:30 +0100 Subject: [PATCH 170/189] CI: Upgrade staticcheck from 2022.1 to 2025.1.1 --- .github/workflows/testing.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index f041a9e..cb7bcae 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -86,6 +86,6 @@ jobs: - name: Run staticcheck (Go ${{ matrix.go }}) uses: dominikh/staticcheck-action@v1.3.1 with: - version: "2022.1" + version: "2025.1.1" install-go: false - cache-key: staticcheck-cache \ No newline at end of file + cache-key: ${{ matrix.go-version }}-${{ matrix.os }} \ No newline at end of file From 3b4a269077b143cf1393a7cbb4edad06811c1343 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 13:40:13 +0100 Subject: [PATCH 171/189] CI: Redefine PR label configuration --- .github/labeler.yml | 18 +++++++++++++++--- .github/workflows/label.yml | 4 +--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 7d38c71..44283d0 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,3 +1,15 @@ -jira-onpremise: onpremise/**/* -jira-cloud: cloud/**/* -documentation: docs/**/* \ No newline at end of file +jira-onpremise: +- changed-files: + - any-glob-to-any-file: onpremise/** + +jira-cloud: +- changed-files: + - any-glob-to-any-file: cloud/** + +documentation: +- changed-files: + - any-glob-to-any-file: docs/** + +examples: +- changed-files: + - any-glob-to-any-file: ['onpremise/examples/**', 'cloud/examples/**'] \ No newline at end of file diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml index f07b674..6924bc2 100644 --- a/.github/workflows/label.yml +++ b/.github/workflows/label.yml @@ -14,6 +14,4 @@ jobs: runs-on: ubuntu-latest if: (github.actor != 'dependabot[bot]') steps: - - uses: actions/labeler@v5 - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file + - uses: actions/labeler@v5 \ No newline at end of file From 5bc984c2ca5bf2e2a0ee77540987e48a8adedddc Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 13:48:55 +0100 Subject: [PATCH 172/189] go mod tidy --- go.mod | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index fe8a80e..c4ef2fc 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/andygrunwald/go-jira/v2 -go 1.18 +go 1.21 + +toolchain go1.24.1 require ( github.com/fatih/structs v1.1.0 From 278db541302885f3483d09ac4b0bb37e786158d7 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 13:56:53 +0100 Subject: [PATCH 173/189] go.mod: Set toolchain go1.23 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c4ef2fc..dbae353 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/andygrunwald/go-jira/v2 go 1.21 -toolchain go1.24.1 +toolchain go1.23 require ( github.com/fatih/structs v1.1.0 From edbfe3f9bca11262948035ea2a416c9a1af4c311 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 13:58:44 +0100 Subject: [PATCH 174/189] CI: Set correct workflow trigger for merge conflicts and documenation workflow --- .github/workflows/documentation.yml | 4 +++- .github/workflows/label-merge-conflicts.yml | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 161807d..d931c2a 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,5 +1,7 @@ -name: Documentation +name: Documentation + on: + workflow_dispatch: push: branches: - main diff --git a/.github/workflows/label-merge-conflicts.yml b/.github/workflows/label-merge-conflicts.yml index 048cbff..81e4567 100644 --- a/.github/workflows/label-merge-conflicts.yml +++ b/.github/workflows/label-merge-conflicts.yml @@ -1,6 +1,7 @@ name: Auto-label merge conflicts on: + pull_request: workflow_dispatch: schedule: - cron: "*/15 * * * *" From 6b5c698a0761aa8238555c0f91c9637ec39deedd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 22 Mar 2025 13:03:08 +0000 Subject: [PATCH 175/189] chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2 Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.1 to 4.5.2. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.1...v4.5.2) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dbae353..b15ffa8 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.23 require ( github.com/fatih/structs v1.1.0 - github.com/golang-jwt/jwt/v4 v4.5.1 + github.com/golang-jwt/jwt/v4 v4.5.2 github.com/google/go-cmp v0.7.0 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 diff --git a/go.sum b/go.sum index 0e9ed30..e631c99 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= -github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= From 426004a4b10210fd8d42040b938c177410b9460f Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 14:13:13 +0100 Subject: [PATCH 176/189] Remove dependency `golang.org/x/term` as it was only used in examples --- cloud/examples/addlabel/main.go | 28 ++++--------------- cloud/examples/create/main.go | 18 ++---------- cloud/examples/createwithcustomfields/main.go | 24 ++++------------ cloud/examples/renderedfields/main.go | 28 ++++--------------- cloud/examples/searchpages/main.go | 24 ++++------------ go.mod | 3 -- go.sum | 4 --- onpremise/examples/addlabel/main.go | 28 ++++--------------- onpremise/examples/basicauth/main.go | 19 ++----------- onpremise/examples/create/main.go | 18 ++---------- .../examples/createwithcustomfields/main.go | 25 ++++------------- onpremise/examples/renderedfields/main.go | 28 ++++--------------- onpremise/examples/searchpages/main.go | 24 ++++------------ 13 files changed, 51 insertions(+), 220 deletions(-) diff --git a/cloud/examples/addlabel/main.go b/cloud/examples/addlabel/main.go index dc6a397..8ab9136 100644 --- a/cloud/examples/addlabel/main.go +++ b/cloud/examples/addlabel/main.go @@ -1,38 +1,20 @@ package main import ( - "bufio" "context" "fmt" "io" - "os" "strings" - "syscall" jira "github.com/andygrunwald/go-jira/v2/cloud" - "golang.org/x/term" ) func main() { - r := bufio.NewReader(os.Stdin) - - fmt.Print("Jira URL: ") - jiraURL, _ := r.ReadString('\n') - - fmt.Print("Jira Username: ") - username, _ := r.ReadString('\n') - - fmt.Print("Jira Password: ") - bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) - password := string(bytePassword) - - fmt.Print("Jira Issue ID: ") - issueId, _ := r.ReadString('\n') - issueId = strings.TrimSpace(issueId) - - fmt.Print("Label: ") - label, _ := r.ReadString('\n') - label = strings.TrimSpace(label) + jiraURL := "https://issues.apache.org/jira/" + username := "my.username" + password := "my.secret.password" + issueId := "MESOS-3325" + label := "example-label" tp := jira.BasicAuthTransport{ Username: strings.TrimSpace(username), diff --git a/cloud/examples/create/main.go b/cloud/examples/create/main.go index df17533..2391f16 100644 --- a/cloud/examples/create/main.go +++ b/cloud/examples/create/main.go @@ -1,29 +1,17 @@ package main import ( - "bufio" "context" "fmt" - "os" "strings" - "syscall" jira "github.com/andygrunwald/go-jira/v2/cloud" - "golang.org/x/term" ) func main() { - r := bufio.NewReader(os.Stdin) - - fmt.Print("Jira URL: ") - jiraURL, _ := r.ReadString('\n') - - fmt.Print("Jira Username: ") - username, _ := r.ReadString('\n') - - fmt.Print("Jira Password: ") - bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) - password := string(bytePassword) + jiraURL := "https://issues.apache.org/jira/" + username := "my.username" + password := "my.secret.password" tp := jira.BasicAuthTransport{ Username: strings.TrimSpace(username), diff --git a/cloud/examples/createwithcustomfields/main.go b/cloud/examples/createwithcustomfields/main.go index 4c1b035..4ebd7a0 100644 --- a/cloud/examples/createwithcustomfields/main.go +++ b/cloud/examples/createwithcustomfields/main.go @@ -1,36 +1,22 @@ package main import ( - "bufio" "context" "fmt" "os" "strings" - "syscall" jira "github.com/andygrunwald/go-jira/v2/cloud" "github.com/trivago/tgo/tcontainer" - "golang.org/x/term" ) func main() { - r := bufio.NewReader(os.Stdin) + jiraURL := "https://issues.apache.org/jira/" + username := "my.username" + password := "my.secret.password" - fmt.Print("Jira URL: ") - jiraURL, _ := r.ReadString('\n') - - fmt.Print("Jira Username: ") - username, _ := r.ReadString('\n') - - fmt.Print("Jira Password: ") - bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) - password := string(bytePassword) - - fmt.Print("Custom field name (i.e. customfield_10220): ") - customFieldName, _ := r.ReadString('\n') - - fmt.Print("Custom field value: ") - customFieldValue, _ := r.ReadString('\n') + customFieldName := "customfield_10220" + customFieldValue := "foo" tp := jira.BasicAuthTransport{ Username: strings.TrimSpace(username), diff --git a/cloud/examples/renderedfields/main.go b/cloud/examples/renderedfields/main.go index b9ec26b..53ecbe9 100644 --- a/cloud/examples/renderedfields/main.go +++ b/cloud/examples/renderedfields/main.go @@ -1,35 +1,19 @@ package main import ( - "bufio" "context" "fmt" "net/http" - "os" "strings" - "syscall" - - "golang.org/x/term" jira "github.com/andygrunwald/go-jira/v2/cloud" ) func main() { - r := bufio.NewReader(os.Stdin) - - fmt.Print("Jira URL: ") - jiraURL, _ := r.ReadString('\n') - - fmt.Print("Jira Issue key: ") - key, _ := r.ReadString('\n') - key = strings.TrimSpace(key) - - fmt.Print("Jira Username: ") - username, _ := r.ReadString('\n') - - fmt.Print("Jira Password: ") - bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) - password := string(bytePassword) + jiraURL := "https://issues.apache.org/jira/" + username := "my.username" + password := "my.secret.password" + issueId := "MESOS-3325" var tp *http.Client @@ -50,10 +34,10 @@ func main() { return } - fmt.Printf("Targeting %s for issue %s\n", strings.TrimSpace(jiraURL), key) + fmt.Printf("Targeting %s for issue %s\n", strings.TrimSpace(jiraURL), issueId) options := &jira.GetQueryOptions{Expand: "renderedFields"} - u, _, err := client.Issue.Get(context.Background(), key, options) + u, _, err := client.Issue.Get(context.Background(), issueId, options) if err != nil { fmt.Printf("\n==> error: %v\n", err) diff --git a/cloud/examples/searchpages/main.go b/cloud/examples/searchpages/main.go index d3a1198..3793c83 100644 --- a/cloud/examples/searchpages/main.go +++ b/cloud/examples/searchpages/main.go @@ -1,34 +1,20 @@ package main import ( - "bufio" "context" "fmt" "log" - "os" "strings" - "syscall" "time" jira "github.com/andygrunwald/go-jira/v2/cloud" - "golang.org/x/term" ) func main() { - r := bufio.NewReader(os.Stdin) - - fmt.Print("Jira URL: ") - jiraURL, _ := r.ReadString('\n') - - fmt.Print("Jira Username: ") - username, _ := r.ReadString('\n') - - fmt.Print("Jira Password: ") - bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) - password := string(bytePassword) - - fmt.Print("\nJira Project Key: ") // e.g. TES or WOW - jiraPK, _ := r.ReadString('\n') + jiraURL := "https://issues.apache.org/jira/" + username := "my.username" + password := "my.secret.password" + jiraProjectKey := "TES" tp := jira.BasicAuthTransport{ Username: strings.TrimSpace(username), @@ -50,7 +36,7 @@ func main() { // SearchPages will page through results and pass each issue to appendFunc // In this example, we'll search for all the issues in the target project - err = client.Issue.SearchPages(context.Background(), fmt.Sprintf(`project=%s`, strings.TrimSpace(jiraPK)), nil, appendFunc) + err = client.Issue.SearchPages(context.Background(), fmt.Sprintf(`project=%s`, strings.TrimSpace(jiraProjectKey)), nil, appendFunc) if err != nil { log.Fatal(err) } diff --git a/go.mod b/go.mod index b15ffa8..835419d 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,4 @@ require ( github.com/google/go-cmp v0.7.0 github.com/google/go-querystring v1.1.0 github.com/trivago/tgo v1.0.7 - golang.org/x/term v0.29.0 ) - -require golang.org/x/sys v0.30.0 // indirect diff --git a/go.sum b/go.sum index e631c99..725a95d 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,4 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/onpremise/examples/addlabel/main.go b/onpremise/examples/addlabel/main.go index 0d383ab..8143d4a 100644 --- a/onpremise/examples/addlabel/main.go +++ b/onpremise/examples/addlabel/main.go @@ -1,38 +1,20 @@ package main import ( - "bufio" "context" "fmt" "io" - "os" "strings" - "syscall" jira "github.com/andygrunwald/go-jira/v2/onpremise" - "golang.org/x/term" ) func main() { - r := bufio.NewReader(os.Stdin) - - fmt.Print("Jira URL: ") - jiraURL, _ := r.ReadString('\n') - - fmt.Print("Jira Username: ") - username, _ := r.ReadString('\n') - - fmt.Print("Jira Password: ") - bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) - password := string(bytePassword) - - fmt.Print("Jira Issue ID: ") - issueId, _ := r.ReadString('\n') - issueId = strings.TrimSpace(issueId) - - fmt.Print("Label: ") - label, _ := r.ReadString('\n') - label = strings.TrimSpace(label) + jiraURL := "https://issues.apache.org/jira/" + username := "my.username" + password := "my.secret.password" + issueId := "MESOS-3325" + label := "example-label" tp := jira.BasicAuthTransport{ Username: strings.TrimSpace(username), diff --git a/onpremise/examples/basicauth/main.go b/onpremise/examples/basicauth/main.go index a728e44..3a6c6fc 100644 --- a/onpremise/examples/basicauth/main.go +++ b/onpremise/examples/basicauth/main.go @@ -1,30 +1,17 @@ package main import ( - "bufio" "context" "fmt" - "os" "strings" - "syscall" - - "golang.org/x/term" jira "github.com/andygrunwald/go-jira/v2/onpremise" ) func main() { - r := bufio.NewReader(os.Stdin) - - fmt.Print("Jira URL: ") - jiraURL, _ := r.ReadString('\n') - - fmt.Print("Jira Username: ") - username, _ := r.ReadString('\n') - - fmt.Print("Jira Password: ") - bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) - password := string(bytePassword) + jiraURL := "https://issues.apache.org/jira/" + username := "my.username" + password := "my.secret.password" tp := jira.BasicAuthTransport{ Username: strings.TrimSpace(username), diff --git a/onpremise/examples/create/main.go b/onpremise/examples/create/main.go index b99b7c8..d1e6ced 100644 --- a/onpremise/examples/create/main.go +++ b/onpremise/examples/create/main.go @@ -1,29 +1,17 @@ package main import ( - "bufio" "context" "fmt" - "os" "strings" - "syscall" jira "github.com/andygrunwald/go-jira/v2/onpremise" - "golang.org/x/term" ) func main() { - r := bufio.NewReader(os.Stdin) - - fmt.Print("Jira URL: ") - jiraURL, _ := r.ReadString('\n') - - fmt.Print("Jira Username: ") - username, _ := r.ReadString('\n') - - fmt.Print("Jira Password: ") - bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) - password := string(bytePassword) + jiraURL := "https://issues.apache.org/jira/" + username := "my.username" + password := "my.secret.password" tp := jira.BasicAuthTransport{ Username: strings.TrimSpace(username), diff --git a/onpremise/examples/createwithcustomfields/main.go b/onpremise/examples/createwithcustomfields/main.go index 2eb954c..ff3843f 100644 --- a/onpremise/examples/createwithcustomfields/main.go +++ b/onpremise/examples/createwithcustomfields/main.go @@ -1,36 +1,21 @@ package main import ( - "bufio" "context" "fmt" "os" "strings" - "syscall" jira "github.com/andygrunwald/go-jira/v2/onpremise" "github.com/trivago/tgo/tcontainer" - "golang.org/x/term" ) func main() { - r := bufio.NewReader(os.Stdin) - - fmt.Print("Jira URL: ") - jiraURL, _ := r.ReadString('\n') - - fmt.Print("Jira Username: ") - username, _ := r.ReadString('\n') - - fmt.Print("Jira Password: ") - bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) - password := string(bytePassword) - - fmt.Print("Custom field name (i.e. customfield_10220): ") - customFieldName, _ := r.ReadString('\n') - - fmt.Print("Custom field value: ") - customFieldValue, _ := r.ReadString('\n') + jiraURL := "https://issues.apache.org/jira/" + username := "my.username" + password := "my.secret.password" + customFieldName := "customfield_10220" + customFieldValue := "foo" tp := jira.BasicAuthTransport{ Username: strings.TrimSpace(username), diff --git a/onpremise/examples/renderedfields/main.go b/onpremise/examples/renderedfields/main.go index 9805902..ea6b1ef 100644 --- a/onpremise/examples/renderedfields/main.go +++ b/onpremise/examples/renderedfields/main.go @@ -1,35 +1,19 @@ package main import ( - "bufio" "context" "fmt" "net/http" - "os" "strings" - "syscall" - - "golang.org/x/term" jira "github.com/andygrunwald/go-jira/v2/onpremise" ) func main() { - r := bufio.NewReader(os.Stdin) - - fmt.Print("Jira URL: ") - jiraURL, _ := r.ReadString('\n') - - fmt.Print("Jira Issue key: ") - key, _ := r.ReadString('\n') - key = strings.TrimSpace(key) - - fmt.Print("Jira Username: ") - username, _ := r.ReadString('\n') - - fmt.Print("Jira Password: ") - bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) - password := string(bytePassword) + jiraURL := "https://issues.apache.org/jira/" + username := "my.username" + password := "my.secret.password" + issueId := "MESOS-3325" var tp *http.Client @@ -50,10 +34,10 @@ func main() { return } - fmt.Printf("Targeting %s for issue %s\n", strings.TrimSpace(jiraURL), key) + fmt.Printf("Targeting %s for issue %s\n", strings.TrimSpace(jiraURL), issueId) options := &jira.GetQueryOptions{Expand: "renderedFields"} - u, _, err := client.Issue.Get(context.Background(), key, options) + u, _, err := client.Issue.Get(context.Background(), issueId, options) if err != nil { fmt.Printf("\n==> error: %v\n", err) diff --git a/onpremise/examples/searchpages/main.go b/onpremise/examples/searchpages/main.go index 0e12a66..c61a283 100644 --- a/onpremise/examples/searchpages/main.go +++ b/onpremise/examples/searchpages/main.go @@ -1,34 +1,20 @@ package main import ( - "bufio" "context" "fmt" "log" - "os" "strings" - "syscall" "time" jira "github.com/andygrunwald/go-jira/v2/onpremise" - "golang.org/x/term" ) func main() { - r := bufio.NewReader(os.Stdin) - - fmt.Print("Jira URL: ") - jiraURL, _ := r.ReadString('\n') - - fmt.Print("Jira Username: ") - username, _ := r.ReadString('\n') - - fmt.Print("Jira Password: ") - bytePassword, _ := term.ReadPassword(int(syscall.Stdin)) - password := string(bytePassword) - - fmt.Print("\nJira Project Key: ") // e.g. TES or WOW - jiraPK, _ := r.ReadString('\n') + jiraURL := "https://issues.apache.org/jira/" + username := "my.username" + password := "my.secret.password" + jiraProjectKey := "TES" tp := jira.BasicAuthTransport{ Username: strings.TrimSpace(username), @@ -50,7 +36,7 @@ func main() { // SearchPages will page through results and pass each issue to appendFunc // In this example, we'll search for all the issues in the target project - err = client.Issue.SearchPages(context.Background(), fmt.Sprintf(`project=%s`, strings.TrimSpace(jiraPK)), nil, appendFunc) + err = client.Issue.SearchPages(context.Background(), fmt.Sprintf(`project=%s`, strings.TrimSpace(jiraProjectKey)), nil, appendFunc) if err != nil { log.Fatal(err) } From 1268fc81765ee981d4d63053100ca112ef9e46cf Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 14:31:11 +0100 Subject: [PATCH 177/189] CI: Switch Github action to label PRs with merge conflict --- .github/workflows/label-merge-conflicts.yml | 27 +++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/label-merge-conflicts.yml b/.github/workflows/label-merge-conflicts.yml index 81e4567..43e3cfe 100644 --- a/.github/workflows/label-merge-conflicts.yml +++ b/.github/workflows/label-merge-conflicts.yml @@ -1,22 +1,23 @@ name: Auto-label merge conflicts on: - pull_request: workflow_dispatch: - schedule: - - cron: "*/15 * * * *" - -# limit permissions -permissions: - contents: read - pull-requests: write + # So that PRs touching the same files as the push are updated + push: + # So that the `dirtyLabel` is removed if conflicts are resolve + # We recommend `pull_request_target` so that github secrets are available. + # In `pull_request` we wouldn't be able to change labels of fork PRs + pull_request_target: + types: [synchronize] jobs: - conflicts: + main: runs-on: ubuntu-latest - steps: - - uses: mschilde/auto-label-merge-conflicts@v2.0 + - name: check if prs are dirty + uses: eps1lon/actions-label-merge-conflict@v3 with: - CONFLICT_LABEL_NAME: conflicts - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file + dirtyLabel: "conflicts" + repoToken: "${{ secrets.GITHUB_TOKEN }}" + commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request." + commentOnClean: "Conflicts have been resolved. A maintainer will review the pull request shortly." \ No newline at end of file From 459e107537fb21649625a3f925ecd76769bcdbfb Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 16:29:12 +0100 Subject: [PATCH 178/189] CI: Create one off label jobs to re-iterate over all PRs --- .github/workflows/label-pull-requests-all.yml | 30 +++++++++++++++++++ .../{label.yml => label-pull-requests.yml} | 9 +++--- 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/label-pull-requests-all.yml rename .github/workflows/{label.yml => label-pull-requests.yml} (65%) diff --git a/.github/workflows/label-pull-requests-all.yml b/.github/workflows/label-pull-requests-all.yml new file mode 100644 index 0000000..483e1a3 --- /dev/null +++ b/.github/workflows/label-pull-requests-all.yml @@ -0,0 +1,30 @@ +# This workflow is a "one off" workflow. +# It re-iterates over all pull requests and labels them. +# This is useful if you have added new labels and want to apply them to all existing pull requests. +name: Label pull requests (all) + +on: + workflow_dispatch: + +# limit permissions +permissions: + contents: read + pull-requests: write + +jobs: + labeler: + runs-on: ubuntu-latest + if: (github.actor != 'dependabot[bot]') + steps: + # Get all Pull Request numbers + - run: | + listOpenIssues="$(gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq ".[].number" + + echo 'LIST_OPEN_ISSUES='$listOpenIssues >> $GITHUB_ENV + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/labeler@v5 + with: + sync-labels: true + pr-number: "$LIST_OPEN_ISSUES" diff --git a/.github/workflows/label.yml b/.github/workflows/label-pull-requests.yml similarity index 65% rename from .github/workflows/label.yml rename to .github/workflows/label-pull-requests.yml index 6924bc2..b715d4b 100644 --- a/.github/workflows/label.yml +++ b/.github/workflows/label-pull-requests.yml @@ -1,8 +1,7 @@ name: Label pull requests on: - pull_request: - workflow_dispatch: + pull_request_target: # limit permissions permissions: @@ -10,8 +9,10 @@ permissions: pull-requests: write jobs: - build: + labeler: runs-on: ubuntu-latest if: (github.actor != 'dependabot[bot]') steps: - - uses: actions/labeler@v5 \ No newline at end of file + - uses: actions/labeler@v5 + with: + sync-labels: true \ No newline at end of file From 3791d778fc51b3dd7884ce7d0dcacbd5e29bad33 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 16:36:31 +0100 Subject: [PATCH 179/189] CI: Fix label-pull-requests-all workflow (shell syntax error) --- .github/workflows/label-pull-requests-all.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label-pull-requests-all.yml b/.github/workflows/label-pull-requests-all.yml index 483e1a3..fe19503 100644 --- a/.github/workflows/label-pull-requests-all.yml +++ b/.github/workflows/label-pull-requests-all.yml @@ -18,7 +18,7 @@ jobs: steps: # Get all Pull Request numbers - run: | - listOpenIssues="$(gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq ".[].number" + listOpenIssues="$(gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq ".[].number") echo 'LIST_OPEN_ISSUES='$listOpenIssues >> $GITHUB_ENV env: From ece0466fd499f75a98ad9de660de6ca9db2c1128 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 16:38:48 +0100 Subject: [PATCH 180/189] CI: Fix label-pull-requests-all workflow (shell syntax error - finally) Sometimes you should be concentrated --- .github/workflows/label-pull-requests-all.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label-pull-requests-all.yml b/.github/workflows/label-pull-requests-all.yml index fe19503..96e3b8c 100644 --- a/.github/workflows/label-pull-requests-all.yml +++ b/.github/workflows/label-pull-requests-all.yml @@ -18,7 +18,7 @@ jobs: steps: # Get all Pull Request numbers - run: | - listOpenIssues="$(gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq ".[].number") + listOpenIssues="$(gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq ''.[].number')" echo 'LIST_OPEN_ISSUES='$listOpenIssues >> $GITHUB_ENV env: From 619405a29b5eedbcccaaf634939b4818123733a0 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 16:41:06 +0100 Subject: [PATCH 181/189] CI: Fix label-pull-requests-all workflow (remove double ') --- .github/workflows/label-pull-requests-all.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label-pull-requests-all.yml b/.github/workflows/label-pull-requests-all.yml index 96e3b8c..6384f85 100644 --- a/.github/workflows/label-pull-requests-all.yml +++ b/.github/workflows/label-pull-requests-all.yml @@ -18,7 +18,7 @@ jobs: steps: # Get all Pull Request numbers - run: | - listOpenIssues="$(gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq ''.[].number')" + listOpenIssues="$(gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq '.[].number')" echo 'LIST_OPEN_ISSUES='$listOpenIssues >> $GITHUB_ENV env: From 4ec9a8f89d7f17095083f73a51c74f57dbc27120 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 16:45:44 +0100 Subject: [PATCH 182/189] CI: Reformat `gh pr` output as string[] --- .github/workflows/label-pull-requests-all.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label-pull-requests-all.yml b/.github/workflows/label-pull-requests-all.yml index 6384f85..caba61d 100644 --- a/.github/workflows/label-pull-requests-all.yml +++ b/.github/workflows/label-pull-requests-all.yml @@ -18,7 +18,7 @@ jobs: steps: # Get all Pull Request numbers - run: | - listOpenIssues="$(gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq '.[].number')" + listOpenIssues="$(gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq 'map(.number | tostring)')" echo 'LIST_OPEN_ISSUES='$listOpenIssues >> $GITHUB_ENV env: From 9ec14adf70e4939d6fe0c9da0c24729208924cbd Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 16:56:04 +0100 Subject: [PATCH 183/189] CI: Use proper GitHub Action output syntax for labeler --- .github/workflows/label-pull-requests-all.yml | 7 ++++--- .github/workflows/label-pull-requests.yml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/label-pull-requests-all.yml b/.github/workflows/label-pull-requests-all.yml index caba61d..b29085e 100644 --- a/.github/workflows/label-pull-requests-all.yml +++ b/.github/workflows/label-pull-requests-all.yml @@ -20,11 +20,12 @@ jobs: - run: | listOpenIssues="$(gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq 'map(.number | tostring)')" - echo 'LIST_OPEN_ISSUES='$listOpenIssues >> $GITHUB_ENV + echo "LIST_OPEN_ISSUES=$listOpenIssues\n" >> $GITHUB_OUTPUT + id: find_all_open_prs env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/labeler@v5 with: - sync-labels: true - pr-number: "$LIST_OPEN_ISSUES" + sync-labels: false + pr-number: ${{ steps.find_all_open_prs.outputs.LIST_OPEN_ISSUES }} diff --git a/.github/workflows/label-pull-requests.yml b/.github/workflows/label-pull-requests.yml index b715d4b..6c5ed19 100644 --- a/.github/workflows/label-pull-requests.yml +++ b/.github/workflows/label-pull-requests.yml @@ -15,4 +15,4 @@ jobs: steps: - uses: actions/labeler@v5 with: - sync-labels: true \ No newline at end of file + sync-labels: false \ No newline at end of file From 1a5d224ca37ef99bcb466fb34e416b7980e10a94 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 16:59:15 +0100 Subject: [PATCH 184/189] CI: Try to use PR numbers as multi line comment --- .github/workflows/label-pull-requests-all.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/label-pull-requests-all.yml b/.github/workflows/label-pull-requests-all.yml index b29085e..a6bca2b 100644 --- a/.github/workflows/label-pull-requests-all.yml +++ b/.github/workflows/label-pull-requests-all.yml @@ -25,7 +25,11 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Check Pull Requests + run: echo ${{ steps.find_all_open_prs.outputs.LIST_OPEN_ISSUES }} + - uses: actions/labeler@v5 with: sync-labels: false - pr-number: ${{ steps.find_all_open_prs.outputs.LIST_OPEN_ISSUES }} + pr-number: | + ${{ steps.find_all_open_prs.outputs.LIST_OPEN_ISSUES }} From 7254fa37a037c7a222c24faf658ce628612b7c19 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 17:01:54 +0100 Subject: [PATCH 185/189] CI: Retrieve Pull Request Numbers as raw list --- .github/workflows/label-pull-requests-all.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label-pull-requests-all.yml b/.github/workflows/label-pull-requests-all.yml index a6bca2b..a8d29da 100644 --- a/.github/workflows/label-pull-requests-all.yml +++ b/.github/workflows/label-pull-requests-all.yml @@ -18,7 +18,7 @@ jobs: steps: # Get all Pull Request numbers - run: | - listOpenIssues="$(gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq 'map(.number | tostring)')" + listOpenIssues="$(gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq '.[].number')" echo "LIST_OPEN_ISSUES=$listOpenIssues\n" >> $GITHUB_OUTPUT id: find_all_open_prs From 190744d9f3c7f706294ee5e22c7450224baaf7e2 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 17:05:34 +0100 Subject: [PATCH 186/189] CI: Write LIST_OPEN_ISSUES as multi-line string to $GITHUB_OUTPUT --- .github/workflows/label-pull-requests-all.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/label-pull-requests-all.yml b/.github/workflows/label-pull-requests-all.yml index a8d29da..aae053c 100644 --- a/.github/workflows/label-pull-requests-all.yml +++ b/.github/workflows/label-pull-requests-all.yml @@ -20,7 +20,9 @@ jobs: - run: | listOpenIssues="$(gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq '.[].number')" - echo "LIST_OPEN_ISSUES=$listOpenIssues\n" >> $GITHUB_OUTPUT + echo 'LIST_OPEN_ISSUES<> $GITHUB_OUTPUT + echo $listOpenIssues >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT id: find_all_open_prs env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From ee10debf69ab53822ed0291bf9fc7d23ca3e53f6 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 22 Mar 2025 17:11:41 +0100 Subject: [PATCH 187/189] CI: We run the labeler now manually one time I tried to fix this automatically. It is cumbersome testing the changes. We don't adjust the label configuration often. Manual execution is fine in this case. --- .github/workflows/label-pull-requests-all.yml | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/.github/workflows/label-pull-requests-all.yml b/.github/workflows/label-pull-requests-all.yml index aae053c..b2278aa 100644 --- a/.github/workflows/label-pull-requests-all.yml +++ b/.github/workflows/label-pull-requests-all.yml @@ -16,22 +16,35 @@ jobs: runs-on: ubuntu-latest if: (github.actor != 'dependabot[bot]') steps: - # Get all Pull Request numbers - - run: | - listOpenIssues="$(gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq '.[].number')" - - echo 'LIST_OPEN_ISSUES<> $GITHUB_OUTPUT - echo $listOpenIssues >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT - id: find_all_open_prs - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Check Pull Requests - run: echo ${{ steps.find_all_open_prs.outputs.LIST_OPEN_ISSUES }} - - uses: actions/labeler@v5 with: sync-labels: false + # Output of gh pr list --repo andygrunwald/go-jira --state open --limit 100 --json number --jq ".[].number" pr-number: | - ${{ steps.find_all_open_prs.outputs.LIST_OPEN_ISSUES }} + 695 + 683 + 682 + 679 + 676 + 670 + 658 + 655 + 648 + 646 + 640 + 637 + 635 + 630 + 626 + 611 + 595 + 499 + 471 + 465 + 461 + 431 + 425 + 398 + 372 + 369 + 300 From 37cfdc3aa82e701fcb58f624bf383df838ad92e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Jul 2025 11:11:14 +0000 Subject: [PATCH 188/189] chore(deps): bump dominikh/staticcheck-action from 1.3.1 to 1.4.0 Bumps [dominikh/staticcheck-action](https://github.com/dominikh/staticcheck-action) from 1.3.1 to 1.4.0. - [Release notes](https://github.com/dominikh/staticcheck-action/releases) - [Changelog](https://github.com/dominikh/staticcheck-action/blob/master/CHANGES.md) - [Commits](https://github.com/dominikh/staticcheck-action/compare/v1.3.1...v1.4.0) --- updated-dependencies: - dependency-name: dominikh/staticcheck-action dependency-version: 1.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index cb7bcae..15adc78 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -84,7 +84,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Run staticcheck (Go ${{ matrix.go }}) - uses: dominikh/staticcheck-action@v1.3.1 + uses: dominikh/staticcheck-action@v1.4.0 with: version: "2025.1.1" install-go: false From 248564b8d2ed37f8823dfe9621ec507fa6e6a8fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 11:01:50 +0000 Subject: [PATCH 189/189] chore(deps): bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/documentation.yml | 2 +- .github/workflows/testing.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index d931c2a..74e03bc 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -10,7 +10,7 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: 3.x diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 15adc78..b5c8200 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -20,7 +20,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} @@ -38,7 +38,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Go ${{ matrix.go-version }} uses: actions/setup-go@v5 with: @@ -58,7 +58,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Go ${{ matrix.go-version }} uses: actions/setup-go@v5 with: @@ -77,7 +77,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Go ${{ matrix.go-version }} uses: actions/setup-go@v5 with: