From f42e3a7f1ec502d8a288f1c550feacff0bfda4bb Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 9 Apr 2022 08:57:48 +0200 Subject: [PATCH 001/154] Fix staticcheck on Go v1.18 and pin it to v2022.1 --- .github/workflows/testing.yml | 4 +++- Makefile | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 94d4a7df..c07122e1 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -41,7 +41,9 @@ jobs: run: make vet - name: Run staticcheck - run: make staticcheck + uses: dominikh/staticcheck-action@v1.1.0 + with: + version: "2022.1" - name: Run Unit tests. run: make test diff --git a/Makefile b/Makefile index 928c554c..e9b063d7 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 get -u honnef.co/go/tools/cmd/staticcheck + go install honnef.co/go/tools/cmd/staticcheck@2022.1 staticcheck ./... .PHONY: all From 7ddae225104930b6e18664eeb1c072d0e7197681 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 9 Apr 2022 09:02:43 +0200 Subject: [PATCH 002/154] GitHub Actions: Switch staticcheck back to make --- .github/workflows/testing.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index c07122e1..94d4a7df 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -41,9 +41,7 @@ jobs: run: make vet - name: Run staticcheck - uses: dominikh/staticcheck-action@v1.1.0 - with: - version: "2022.1" + run: make staticcheck - name: Run Unit tests. run: make test From 1998d7106fbc00aa884587fccca0bd81fb2c3c7f Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 9 Apr 2022 09:07:32 +0200 Subject: [PATCH 003/154] GitHub Actions: Switch v2022.1 to master version of staticcheck --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e9b063d7..7adc7f9a 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@master staticcheck ./... .PHONY: all From 42f2bd200db2a448ade0a4f69c306d12cf48bcbd Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 9 Apr 2022 09:11:00 +0200 Subject: [PATCH 004/154] staticcheck: Remove caching for modules and switch back to static version --- .github/workflows/testing.yml | 16 ++++++---------- Makefile | 2 +- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 94d4a7df..20e7cf5c 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: [ '1.18', '1.17' ] + go: [ '1.18', '1.17' ] os: ["windows-latest", "ubuntu-latest", "macOS-latest"] runs-on: ${{ matrix.os }} @@ -25,14 +25,6 @@ jobs: with: go-version: ${{ matrix.go-version }} - # Caching go modules to speed up the run - - uses: actions/cache@v3.0.1 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - name: Run go fmt if: runner.os != 'Windows' run: diff -u <(echo -n) <(gofmt -d -s .) @@ -41,7 +33,11 @@ jobs: run: make vet - name: Run staticcheck - run: make staticcheck + uses: dominikh/staticcheck-action@v1.1.0 + with: + version: "2022.1" + install-go: false + cache-key: ${{ matrix.go }} - name: Run Unit tests. run: make test diff --git a/Makefile b/Makefile index 7adc7f9a..e9b063d7 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@master + go install honnef.co/go/tools/cmd/staticcheck@2022.1 staticcheck ./... .PHONY: all From 50be56762f6ce97a9fbb9970f048eac302f5af4f Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 9 Apr 2022 09:52:28 +0200 Subject: [PATCH 005/154] GitHub Actions: Upgrade staticcheck action version to v1.2.0 (#451) * GitHub Actions: Upgrade staticcheck action version to v1.2.0 * GitHub Actions: Fix go-version of setup-go * Set version golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f See https://github.com/google/go-jsonnet/issues/596 and https://github.com/felder/go-jsonnet/commit/79497457edac948b29bc6b5ee55acce006ac50ac --- .github/workflows/testing.yml | 12 ++++++------ go.mod | 1 + go.sum | 2 ++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 20e7cf5c..c44aedb7 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -16,28 +16,28 @@ jobs: fail-fast: false matrix: go: [ '1.18', '1.17' ] - os: ["windows-latest", "ubuntu-latest", "macOS-latest"] + os: [ 'windows-latest', 'ubuntu-latest', 'macOS-latest' ] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v2 with: - go-version: ${{ matrix.go-version }} + go-version: ${{ matrix.go }} - - name: Run go fmt + - name: Run go fmt (Go ${{ matrix.go }}) if: runner.os != 'Windows' run: diff -u <(echo -n) <(gofmt -d -s .) - name: Run go vet run: make vet - - name: Run staticcheck - uses: dominikh/staticcheck-action@v1.1.0 + - 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. + - name: Run Unit tests (Go ${{ matrix.go }}) run: make test diff --git a/go.mod b/go.mod index 1f2f8855..ba7f70c5 100644 --- a/go.mod +++ b/go.mod @@ -10,4 +10,5 @@ require ( github.com/pkg/errors v0.9.1 github.com/trivago/tgo v1.0.7 golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d + golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f // indirect ) diff --git a/go.sum b/go.sum index 2e82b12f..aa0e3b87 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ 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 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= 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/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= From 9465594f9c2ccb22660ee975b829d1d635ae11bc Mon Sep 17 00:00:00 2001 From: Luciano da Silva Ribas Date: Sat, 9 Apr 2022 04:56:01 -0300 Subject: [PATCH 006/154] Added an example of how to add a label to an issue (#442) closes #441 Co-authored-by: Luciano da Silva Ribas --- examples/addlabel/main.go | 77 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 examples/addlabel/main.go diff --git a/examples/addlabel/main.go b/examples/addlabel/main.go new file mode 100644 index 00000000..7c1de678 --- /dev/null +++ b/examples/addlabel/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "bufio" + "fmt" + "io/ioutil" + "os" + "strings" + "syscall" + + jira "github.com/andygrunwald/go-jira" + "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, _ := ioutil.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) +} From 2add5a7588ab6550bfd3a1091f91c8dd2cb5dd5b Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sat, 9 Apr 2022 09:57:08 +0200 Subject: [PATCH 007/154] GitHub Actions: Remove greetings workflow (#450) It was a nice idea, and I still like it, but I don't see a big benefit of this anymore. --- .github/workflows/greetings.yml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .github/workflows/greetings.yml diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml deleted file mode 100644 index 88eb1e3e..00000000 --- a/.github/workflows/greetings.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Greetings -on: [pull_request, issues] - -jobs: - greeting: - runs-on: ubuntu-latest - steps: - - uses: actions/first-interaction@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - issue-message: 'Hi! Thank you for taking the time to create your first issue! Really cool to see you here for the first time. Please give us a bit of time to review it.' - pr-message: 'Great! Thank you for taking the time to create your first pull request. It is always a pleasure to see people like you who spent time contributing. Please give us a bit of time to review it!' \ No newline at end of file From 7c19ccd589ad9231bb94d7ad2f82d5e06d9dff53 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 08:34:18 +0200 Subject: [PATCH 008/154] chore(deps): bump actions/setup-go from 2 to 3 (#452) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2 to 3. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .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 c44aedb7..225b0b86 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@v2 + - uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} From dcc7f6595594cf330e8fb3039b913f3360fd224d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Apr 2022 13:11:10 +0200 Subject: [PATCH 009/154] chore(deps): bump github.com/google/go-cmp from 0.5.7 to 0.5.8 (#453) Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.7 to 0.5.8. - [Release notes](https://github.com/google/go-cmp/releases) - [Commits](https://github.com/google/go-cmp/compare/v0.5.7...v0.5.8) --- updated-dependencies: - dependency-name: github.com/google/go-cmp dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index ba7f70c5..55206664 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,10 @@ go 1.15 require ( github.com/fatih/structs v1.1.0 github.com/golang-jwt/jwt/v4 v4.4.1 - github.com/google/go-cmp v0.5.7 + 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 golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f // indirect + golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d ) diff --git a/go.sum b/go.sum index aa0e3b87..1aedf991 100644 --- a/go.sum +++ b/go.sum @@ -3,19 +3,17 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= github.com/golang-jwt/jwt/v4 v4.4.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.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +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 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= 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/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 47d27a76e84da43f6e27e1cd0f930e6763dc79d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Jul 2022 18:07:02 +0200 Subject: [PATCH 010/154] chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.4.1 to 4.4.2 (#464) Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.4.1 to 4.4.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.4.1...v4.4.2) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v4 dependency-type: direct:production update-type: version-update:semver-patch ... 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 55206664..21eb225a 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.15 require ( github.com/fatih/structs v1.1.0 - github.com/golang-jwt/jwt/v4 v4.4.1 + 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 diff --git a/go.sum b/go.sum index 1aedf991..10f2c320 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.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= -github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +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= From d758a3d5fd61a81677ae9f053c4c300e80f0aa92 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 21 Aug 2022 17:51:38 +0200 Subject: [PATCH 011/154] 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 571d4c30..ed9a6d00 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 012/154] 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 225b0b86..bda05211 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 ed9a6d00..8a74e8bf 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 fcaf8e10..2f41f8c6 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 9235d080..0ceea898 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 11a5dc2d..0806b76e 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 c7bc2e58..c0496422 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 7c1de678..3b0e82d0 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 a2deb533..9bdf3612 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 1b3a6903..5d19fc32 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 21eb225a..0a13bf00 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 0aa03b70..6cb6781a 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 ceeff9a8..a8675351 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 24a3ab0e..a9f7af2a 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 56786629..8e01d83e 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 25c52bae..c86a3088 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 3aedbd64..280bfc72 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 560a2f05..3953ac79 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 8efc1230..373e57ac 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 b91a2367..38258f08 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 6858b400..727267b6 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 7c01e751..c4086311 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 6e801fec..b1a63b68 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 7fbffc0b..f877f862 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 f0f98d6f..6d21e4e0 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 64125dbc..6c8b58a1 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 11a75348..19b1de89 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 3f8b7ecb..f58fc320 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 78d60396..078b82ff 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 bca12a45..45eecd51 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 013/154] 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 8a74e8bf..a35998ca 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 014/154] 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 c0496422..f4cf3de5 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 0a13bf00..e5ebce3d 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 10f2c320..7f282ab5 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 c86a3088..28e0cccc 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 015/154] 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 fab8b0fe..f9741e46 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 016/154] 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 00000000..c68f90fb --- /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 017/154] 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 00000000..7d38c713 --- /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 00000000..c2bd339e --- /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 018/154] 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 bda05211..6d7699fe 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 019/154] 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 c68f90fb..048cbff0 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 c2bd339e..b37fcb9d 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 020/154] :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 a35998ca..4c4dd778 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 021/154] 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 5915ef43..35dd84e3 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 00000000..0f395ea9 --- /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 00000000..a2d76ca7 --- /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 00000000..b0e3c4c1 --- /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 00000000..db4704e8 --- /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 00000000..9ff3f903 --- /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 00000000..bd6e4b4e --- /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 00000000..c9d1cfc1 --- /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 00000000..dc0ac492 --- /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 00000000..e577bb68 --- /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 00000000..fb2f3390 --- /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 00000000..5ad49267 --- /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 2f41f8c6..fc0828c0 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 0ceea898..e94b5edf 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 890d6e0a..25b47832 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 0806b76e..699776cb 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 b76fe0cf..51a36b6a 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 527cdbe6..e0719456 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 3df31c1c..fb43ff95 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 d8c3714d..d12db6b1 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 f4cf3de5..7a8ca2c7 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 f9741e46..cb60c1cd 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 3b0e82d0..d7dc3fa2 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 04a0daed..c9d6eb00 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 00a00c14..b803de09 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 2a6922a3..8ebe424b 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 750e77d3..b13adc2f 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 6fcc602e..e5325bc1 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 9cb05f54..de980db8 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 e0bec521..9b259d8d 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 571e9e12..69a98576 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 b72c946e..92e8aa93 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 754a5a07..f1dac695 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 b14057d9..4cc369a7 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 9bdf3612..55ef9f54 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 f40f3a58..6a0937cc 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 5d19fc32..add1accf 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 f78c6810..39fb2952 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 e4503ddd..98b48f23 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 6cb6781a..dc171831 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 a8675351..01c8d7fa 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 a9f7af2a..a65e88e4 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 8e01d83e..f33a81bc 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 28e0cccc..bd1894b7 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 280bfc72..65b22727 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 3953ac79..40f0ae65 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 a77951a4..15ff7515 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 65f222b3..4950f2bc 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 1af51edd..ad96d12c 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 7af5a8bf..3c118056 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 373e57ac..8cddb72b 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 a7b12a41..2cd5971d 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 38258f08..af6305c2 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 f1000c81..e67fd02d 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 727267b6..be0a4a4c 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 a933a57a..2a0868a6 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 89c73612..4585a655 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 e23d5650..df5282e1 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 c4086311..2ee04872 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 66d223ff..6ebc71bf 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 b1a63b68..88ca0afb 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 f877f862..8ee6c04b 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 909ede3d..e5527050 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 6d21e4e0..e68338cb 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 6c8b58a1..464d0c0d 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 a3703929..0480d567 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 19b1de89..60aa096e 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 bed5c566..c78b03ba 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 f58fc320..9632a0d8 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 b99fc1c3..1dc861c2 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 078b82ff..289ef083 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 ca572dba..f170e8a0 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 45eecd51..e10cc897 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 e00d69e1..3e89c116 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 00000000..3dd39772 --- /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 00000000..f18d1d90 --- /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 00000000..fd638e9b --- /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 00000000..37918f67 --- /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 00000000..ceb6d655 --- /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 00000000..3b768135 --- /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 00000000..19a33bc7 --- /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 00000000..0cb776be --- /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 00000000..ccf308b9 --- /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 00000000..e3b3e8d2 --- /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 00000000..b0ac50f8 --- /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 00000000..4bca28a9 --- /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 00000000..c6c0ee70 --- /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 00000000..52cbfa6f --- /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 00000000..ccdcae85 --- /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 00000000..7c918dd5 --- /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 00000000..bc60e09a --- /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 00000000..82a46a0f --- /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 00000000..db886573 --- /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 00000000..3894890e --- /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 00000000..4238f64b --- /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 00000000..2f0c30ae --- /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 00000000..89952694 --- /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 00000000..b3067606 --- /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 00000000..be125c7a --- /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 00000000..56f02100 --- /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 00000000..76e42dc0 --- /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 00000000..1a78fe94 --- /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 00000000..b63a30f4 --- /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 00000000..f60642aa --- /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 00000000..45cdceed --- /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 00000000..8ba33ad0 --- /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 00000000..166b9c47 --- /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 00000000..763d5b6e --- /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 00000000..07ead725 --- /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 00000000..507753ea --- /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 00000000..e197922e --- /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 00000000..acdb15b6 --- /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 00000000..4f820179 --- /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 00000000..5816f676 --- /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 00000000..c8cf5422 --- /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 00000000..397bb9a3 --- /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 00000000..456708de --- /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 00000000..f9a5f6eb --- /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 00000000..6fff1764 --- /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 00000000..6c6e5fb1 --- /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 00000000..373479bd --- /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 00000000..809d9eea --- /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 00000000..f81794ee --- /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 00000000..206efdf4 --- /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 00000000..1d3e46ba --- /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 00000000..e2ca2736 --- /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 00000000..47782f42 --- /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 00000000..21e583ae --- /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 00000000..3336ce7e --- /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 00000000..886f7ad7 --- /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 00000000..76db3d68 --- /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 00000000..70629cab --- /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 00000000..01d94bf6 --- /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 00000000..524e9c3f --- /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 00000000..d935cc1f --- /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 00000000..5828e125 --- /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 00000000..d7560cac --- /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 00000000..25e743ae --- /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 00000000..93b4ca90 --- /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 00000000..0f9475f8 --- /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 00000000..8918fbe8 --- /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 00000000..e06e7173 --- /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 00000000..7eec4767 --- /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 00000000..bec5009b --- /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 00000000..f76d22ce --- /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 00000000..f4a8266e --- /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 00000000..7ec3ca44 --- /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 9f3e2647..00000000 --- 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 93eb65e8..00000000 --- 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 022/154] 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 35dd84e3..7f90595f 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 db4704e8..c7db1513 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 bd6e4b4e..ddde9460 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 c9d1cfc1..14dde2cc 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 e577bb68..2ce50f28 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 5ad49267..397a8e8f 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 fc0828c0..caa7c312 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 25b47832..7ef0f151 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 51a36b6a..ad18e43b 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 fb43ff95..095c81e5 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 cb60c1cd..b8e55cd2 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 d7dc3fa2..ca5a6f7a 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 c9d6eb00..7221d877 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 b803de09..2dc38371 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 8ebe424b..68b32b5b 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 b13adc2f..eee27470 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 e5325bc1..525cd519 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 de980db8..ce3ba452 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 9b259d8d..c03461dc 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 69a98576..07c52ee3 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 92e8aa93..274498f5 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 f1dac695..51db937d 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 4cc369a7..c53a14f1 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 6a0937cc..a92e0e50 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 39fb2952..29a70d12 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 dc171831..e18d0dbe 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 a65e88e4..9d09eead 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 bd1894b7..f4273b7f 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 65b22727..224af7b0 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 40f0ae65..438a5f39 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 4950f2bc..515f6c20 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 3c118056..3f8edccd 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 2cd5971d..7f62cd46 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 e67fd02d..60e4eae8 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 2a0868a6..6d276ee4 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 df5282e1..b10e44d2 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 6ebc71bf..7854bf3e 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 8ee6c04b..69df04ba 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 e68338cb..e5891596 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 0480d567..181d8ac3 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 c78b03ba..d7da81c1 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 289ef083..acd038cb 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 e10cc897..69b4bac8 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 37918f67..e85cf277 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 3b768135..c63104ea 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 19a33bc7..dbf6b2ba 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 ccf308b9..6332d54b 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 b0ac50f8..b205a5ff 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 4bca28a9..baade93c 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 52cbfa6f..d85e0a09 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 7c918dd5..c1ac05d0 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 82a46a0f..21e9013d 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 4238f64b..4aca07d8 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 2f0c30ae..ca5a6f7a 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 89952694..7221d877 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 b3067606..2dc38371 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 be125c7a..68b32b5b 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 56f02100..eee27470 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 76e42dc0..525cd519 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 1a78fe94..ce3ba452 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 b63a30f4..c03461dc 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 f60642aa..07c52ee3 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 45cdceed..274498f5 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 8ba33ad0..51db937d 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 166b9c47..77477213 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 07ead725..4652429f 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 e197922e..12a0cf58 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 4f820179..e031ae4b 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 c8cf5422..c6af34d3 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 456708de..dea2181f 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 f9a5f6eb..1a0b9e17 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 6fff1764..dd35a7fe 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 373479bd..7a752d5e 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 f81794ee..3257eedb 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 1d3e46ba..9ec0dbef 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 47782f42..6b6e8d7d 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 3336ce7e..b0960057 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 76db3d68..c2613fbf 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 01d94bf6..f3b9f725 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 d935cc1f..c303904f 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 d7560cac..667df05f 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 93b4ca90..0ca307fd 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 8918fbe8..2035d7a1 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 bec5009b..1064c0e5 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 f4a8266e..ec4ce2c7 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 023/154] 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 7f90595f..2429f699 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 e18d0dbe..2ee215f4 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 f4273b7f..f473a93e 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 224af7b0..7bf134e9 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 024/154] 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 e031ae4b..eb087474 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 dea2181f..b98c966f 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 1a0b9e17..e78fc707 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 025/154] 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 e5ebce3d..bf4eca04 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 7f282ab5..6dcc5c06 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 026/154] #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 0dcdd1f0..00000000 --- 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 6d7699fe..81e16725 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 027/154] Changelog: Add note about ...WithContext API methods --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2429f699..134755bc 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 028/154] 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 69b4bac8..aaad00bf 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 3e89c116..3cf52d0e 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 ec4ce2c7..d1edc027 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 7ec3ca44..a43f14d5 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 029/154] 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 ca5a6f7a..64c2c4dc 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 7221d877..e9551714 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 2dc38371..e1b8d602 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 68b32b5b..3d6e5109 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 eee27470..ee9c15ff 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 525cd519..c1ccc145 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 ce3ba452..dfb65dc4 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 c03461dc..17bba158 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 07c52ee3..ab2bb36b 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 274498f5..e979b474 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 51db937d..82bb3ec9 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 030/154] 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 397a8e8f..d2403151 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 7221d877..7aa21168 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 2ee215f4..8570060b 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 acd038cb..ade589cd 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 f170e8a0..5964874e 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 b205a5ff..bf047981 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 e9551714..127c55e2 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 eb087474..f4b54d99 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 1064c0e5..79003be2 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 f76d22ce..97a4d399 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 031/154] 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 d7da81c1..eea97a47 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 9632a0d8..cad63fee 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 2035d7a1..7e7c8cea 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 e06e7173..aee35ef0 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 032/154] 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 181d8ac3..8d293d55 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 60aa096e..1e71957d 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 0ca307fd..f3e94f2e 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 0f9475f8..a5a9a2f9 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 033/154] 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 e5891596..18340671 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 464d0c0d..f12bd2b3 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 667df05f..25011af4 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 25e743ae..d063a69d 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 034/154] 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 69df04ba..0b9282d6 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 e5527050..a982cdb1 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 c303904f..d1c86ac3 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 5828e125..532a477c 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 035/154] 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 7854bf3e..ed918eb3 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 88ca0afb..a4c7a914 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 f3b9f725..b72c0c78 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 524e9c3f..fba7babd 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 036/154] 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 6d276ee4..286b223e 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 4585a655..bc7d7bd3 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 b0960057..1514979a 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 886f7ad7..fcee64e3 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 037/154] 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 b10e44d2..e08c46d2 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 2ee04872..adcad666 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 c2613fbf..c7f4859e 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 70629cab..ebaa0efc 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 038/154] 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 438a5f39..dde3c839 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 15ff7515..ef720e95 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 dd35a7fe..48d65d65 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 6c6e5fb1..605cd523 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 039/154] 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 3f8edccd..e3afa1d8 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 8cddb72b..299c57a5 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 3257eedb..8f7d3373 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 206efdf4..8cd8c3e3 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 040/154] 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 7f62cd46..a431d745 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 af6305c2..59948883 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 9ec0dbef..8e928aeb 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 e2ca2736..5c15bc4d 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 041/154] 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 ad18e43b..04fc1310 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 e0719456..15bcbbc9 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 c1ac05d0..3d10d6d0 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 bc60e09a..f7f4f66c 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 042/154] 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 095c81e5..d1adc3e4 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 d12db6b1..bce6a12d 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 21e9013d..b7c11c96 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 db886573..835381c8 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 043/154] 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 c53a14f1..5fbbc0ff 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 55ef9f54..93aade9a 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 77477213..054d853c 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 763d5b6e..59cf8311 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 044/154] 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 a92e0e50..52f942b7 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 add1accf..e84fda05 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 4652429f..6bf30747 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 507753ea..d3d9c916 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 045/154] 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 29a70d12..1b30c3aa 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 98b48f23..f95ad44a 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 12a0cf58..98e984c9 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 acdb15b6..8f7e1763 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 046/154] 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 60e4eae8..2e55420e 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 be0a4a4c..8d37ff28 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 6b6e8d7d..b9d72e1c 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 21e583ae..655222f9 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 047/154] 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 515f6c20..a40b9a61 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 ad96d12c..98581df2 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 7a752d5e..dafa7a73 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 809d9eea..fa72e72c 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 048/154] 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 9d09eead..0ab8b03d 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 f33a81bc..23df4f4f 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 c6af34d3..0589247e 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 397bb9a3..91816320 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 049/154] 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 7ef0f151..c1e9504a 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 699776cb..b2861f24 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 d85e0a09..019f4afa 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 ccdcae85..edfcb198 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 050/154] 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 2ce50f28..6a680709 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 ca5a6f7a..12adcb17 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 2dc38371..2734e6c1 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 68b32b5b..0f597f2b 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 525cd519..078a4896 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 ce3ba452..b5161b93 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 c03461dc..dea1ecf0 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 07c52ee3..9a2d2bd5 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 274498f5..8671120a 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 51db937d..c98f5852 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 8570060b..a0b89c1b 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 01c8d7fa..ff0e5a8a 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 6332d54b..b74cae0b 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 64c2c4dc..94e06738 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 e1b8d602..81347030 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 3d6e5109..71cf0964 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 c1ccc145..037603a7 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 dfb65dc4..3c619dc7 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 17bba158..96b1aa6f 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 ab2bb36b..e8ae219c 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 e979b474..e9e81189 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 82bb3ec9..d5255123 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 f4b54d99..dee42b93 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 5816f676..f1d7c764 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 051/154] 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 caa7c312..e0a41660 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 e94b5edf..b0b6d9d2 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 baade93c..62c8b505 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 c6c0ee70..7b602a45 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 052/154] 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 c7db1513..421705eb 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 14dde2cc..1ce43408 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 e0a41660..9ec36fa9 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 b0b6d9d2..0f748d40 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 c1e9504a..1d69dbd3 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 b2861f24..a3a05e10 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 b8e55cd2..dcc51ee5 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 eee27470..a69b435e 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 5fbbc0ff..93cb030b 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 93aade9a..c6f9fe8f 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 52f942b7..65035b28 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 e84fda05..e4fa5f32 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 1b30c3aa..bfd2a5cc 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 f95ad44a..cc9fdfe1 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 a0b89c1b..f500e2af 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 ff0e5a8a..476b408f 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 0ab8b03d..bee53e1e 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 23df4f4f..c9a3e59a 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 7bf134e9..9f4869ce 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 dde3c839..1ae9bc9e 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 ef720e95..7292c67d 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 a40b9a61..2f18316b 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 98581df2..01feba6f 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 e3afa1d8..51a1ee10 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 299c57a5..45032895 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 a431d745..b89e17fe 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 59948883..6fa6f23c 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 2e55420e..2480ece8 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 8d37ff28..f21cceb1 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 e08c46d2..d08fb591 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 adcad666..d003e315 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 ed918eb3..4ab93270 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 a4c7a914..506f1a7a 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 0b9282d6..7a385bf7 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 a982cdb1..043c1f3c 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 18340671..ba9dd216 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 f12bd2b3..4f33dbdb 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 8d293d55..fe796664 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 1e71957d..c433a686 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 eea97a47..74dc4c79 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 cad63fee..f05705a2 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 ade589cd..35972b2b 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 5964874e..b4651025 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 aaad00bf..8bd20662 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 3cf52d0e..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 e85cf277..2c8b2e46 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 dbf6b2ba..076aadc8 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 62c8b505..6c9fa866 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 7b602a45..1453529e 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 019f4afa..b521eb63 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 edfcb198..c05b3455 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 4aca07d8..339b9dcd 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 ee9c15ff..8f1a1c7b 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 054d853c..2bc2489d 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 59cf8311..f89b46e6 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 6bf30747..d861f0ab 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 d3d9c916..55af7fc2 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 98e984c9..e373668a 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 8f7e1763..f9b923e0 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 dee42b93..e1006702 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 f1d7c764..eeda9ef5 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 0589247e..9aee11a3 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 91816320..2a1e53cc 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 e78fc707..5116f844 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 48d65d65..5a5e9700 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 605cd523..9a5c22d7 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 dafa7a73..78542562 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 fa72e72c..c83cf31a 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 8f7d3373..0f5f29d2 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 8cd8c3e3..ace1aa9d 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 8e928aeb..dd0ff7e8 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 5c15bc4d..91edd49e 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 b9d72e1c..364893fa 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 655222f9..2725d540 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 c7f4859e..0dc007af 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 ebaa0efc..ce33192c 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 b72c0c78..08c6dc67 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 fba7babd..8762bdd6 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 d1c86ac3..beeb1dc6 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 532a477c..778292dd 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 25011af4..ff4ff53e 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 d063a69d..a399b01a 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 f3e94f2e..3d9a92c1 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 a5a9a2f9..255d59b9 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 7e7c8cea..3178c63a 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 aee35ef0..93af9d75 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 79003be2..d9f05e77 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 97a4d399..5fd0108c 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 d1edc027..026e1e86 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 a43f14d5..0bddae9b 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 053/154] 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 ddde9460..26565b54 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 9ec36fa9..166d5fdf 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 0f748d40..19bd5e53 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 1d69dbd3..08930622 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 a3a05e10..c190256b 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 04fc1310..ea97461b 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 15bcbbc9..780cfdb5 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 bce6a12d..ecbfee95 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 bfd2a5cc..49f02c13 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 cc9fdfe1..c6acefb9 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 f500e2af..444de794 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 476b408f..8a4086e8 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 bee53e1e..704421ef 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 c9a3e59a..3b0ad076 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 2f18316b..cce347a3 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 01feba6f..471e507e 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 286b223e..c6a1e80a 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 bc7d7bd3..3cfdb61e 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 7a385bf7..ef243b37 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 043c1f3c..8a8f683e 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 ba9dd216..407fe406 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 4f33dbdb..67801ad9 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 35972b2b..4c2691cc 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 b4651025..0f18db98 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 8bd20662..6c79df2c 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..4a1b6c22 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 c63104ea..89ac2a01 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 6c9fa866..be5d4025 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 1453529e..295de5a6 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 b521eb63..7326b8c1 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 c05b3455..baf575d8 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 3d10d6d0..a1b841b8 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 f7f4f66c..28aefaf5 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 835381c8..9cebf77d 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 e373668a..ff2a73eb 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 f9b923e0..a963908b 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 e1006702..9b26e9e3 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 eeda9ef5..ee58b78e 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 9aee11a3..8697f263 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 2a1e53cc..4d20c229 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 78542562..406b48cf 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 c83cf31a..5726f96c 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 1514979a..6d127059 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 fcee64e3..0adc6344 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 beeb1dc6..2f4844e3 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 778292dd..34002aa2 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 ff4ff53e..145f0e9d 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 a399b01a..4b995759 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 d9f05e77..2cf67b65 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 5fd0108c..2397f42e 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 026e1e86..6b12ab96 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 0bddae9b..459f9890 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 054/154] 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 444de794..8d9ca4b9 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 8a4086e8..8409fd6a 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 704421ef..14345761 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 3b0ad076..36dd2a18 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 cce347a3..616e87c7 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 471e507e..2c37754d 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 6c79df2c..b5780c60 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 4a1b6c22..b624f1a8 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 9b26e9e3..58a44d1f 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 ee58b78e..7a4f6bc6 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 8697f263..c8523083 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 4d20c229..8d6ef59e 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 406b48cf..b59a4e50 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 5726f96c..a80d5f3d 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 6b12ab96..697cb514 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 459f9890..ce731c0a 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 055/154] 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 166d5fdf..20d54bf6 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 19bd5e53..47cf5e49 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 08930622..37b5167a 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 c190256b..2495dcef 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 49f02c13..5a3da1c3 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 c6acefb9..43fff232 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 8d9ca4b9..b459cd08 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 8409fd6a..3618b4cb 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 14345761..5664a7bb 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 36dd2a18..1bcbbc67 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 616e87c7..70db2f27 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 2c37754d..64c12312 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 ef243b37..c370c20c 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 8a8f683e..dc4c3f5d 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 4c2691cc..cc397cca 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 0f18db98..192688ed 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 be5d4025..a08cdb8f 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 295de5a6..b9299e45 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 7326b8c1..14045201 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 baf575d8..3ec06aaf 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 ff2a73eb..44074eec 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 a963908b..125db709 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 58a44d1f..45aade5a 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 7a4f6bc6..36186f78 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 c8523083..55990843 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 8d6ef59e..374bebc5 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 b59a4e50..c75ba9d0 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 a80d5f3d..a4e58267 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 2f4844e3..f3da1d99 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 34002aa2..d1c3df48 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 2cf67b65..a2a8c7f3 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 2397f42e..273bc3a0 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 056/154] 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 134755bc..ba003dee 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 057/154] 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 12adcb17..2499c1cd 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 7aa21168..46838003 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 2734e6c1..f398345e 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 0f597f2b..f849c8a8 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 a69b435e..82d6cfa4 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 078a4896..de929678 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 b5161b93..1ea045c8 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 dea1ecf0..ba7841f8 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 9a2d2bd5..3220b312 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 8671120a..6afdede1 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 c98f5852..dd4c2454 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 bf4eca04..b76d7e6f 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 94e06738..986f9c7e 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 127c55e2..bd8b0f3a 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 81347030..cea1db5e 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 71cf0964..e3d185e2 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 8f1a1c7b..209c5d0d 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 037603a7..fc613059 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 3c619dc7..72b942aa 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 96b1aa6f..e6d78c8a 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 e8ae219c..71eaf23e 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 e9e81189..461061a2 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 d5255123..1be33078 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 058/154] 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 2499c1cd..4ece27fc 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 46838003..b04888a6 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 f398345e..ecfdd7cf 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 f849c8a8..22f531fc 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 82d6cfa4..161b6d89 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 de929678..db7b774d 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 1ea045c8..c224eaf7 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 ba7841f8..d1895eb3 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 3220b312..15995e33 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 6afdede1..1fc5de59 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 dd4c2454..56ebebbe 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 986f9c7e..0d383ab9 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 bd8b0f3a..a728e44e 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 cea1db5e..b99b7c8c 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 e3d185e2..2eb954cc 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 209c5d0d..7c54e275 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 fc613059..a031e0fe 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 72b942aa..2509baf4 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 e6d78c8a..35655018 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 71eaf23e..b4a8b749 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 461061a2..98059025 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 1be33078..0e12a661 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 059/154] 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 4228558a..99586f8a 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 edc78bb8..16e8fd73 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 060/154] 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 4a3bf0dc..f18673d6 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 061/154] 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 00000000..460fa5ba --- /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 00000000..000ea345 --- /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 00000000..61df2695 --- /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 062/154] 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 ba003dee..a8f5c9a1 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 37b5167a..992a378d 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 2495dcef..099c424a 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 14045201..8b92be72 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 3ec06aaf..894876b5 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 063/154] 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 a8f5c9a1..130f3844 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 5a3da1c3..6bd765f4 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 43fff232..596ac61e 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 44074eec..07a36943 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 125db709..9ffd6bab 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 064/154] 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 130f3844..0689b120 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 b459cd08..99dc8f01 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 3618b4cb..bae570e1 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 45aade5a..b549afeb 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 36186f78..88bdc207 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 065/154] 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 0689b120..7c5051d2 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 1ae9bc9e..3e6af1ee 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 7292c67d..2182483b 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 5a5e9700..a1b104c9 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 9a5c22d7..a1380768 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 066/154] 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 7c5051d2..21d935fe 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 2480ece8..3fa6d490 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 f21cceb1..046df3d8 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 364893fa..cc035947 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 2725d540..e997a079 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 067/154] 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 e9b063d7..e1f5008c 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 00000000..39fd854f --- /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 00000000..a45da29b --- /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 00000000..368ccaa3 --- /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 068/154] 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 460fa5ba..d4362537 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 069/154] 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 d4362537..7de518cd 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 070/154] 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 20d54bf6..070a9112 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 99dc8f01..e25c522c 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 bae570e1..a8ad6487 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 5664a7bb..c228f06c 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 cc397cca..5bce9e38 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 b5780c60..4c1e1af7 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 a08cdb8f..27bc8c46 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 b549afeb..a8828be8 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 55990843..2177363d 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 a2a8c7f3..b6205ded 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 697cb514..69e4becd 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 071/154] 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 070a9112..e730209b 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 072/154] 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 4c4dd778..925860ac 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 073/154] :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 00000000..02c343d6 --- /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 74dc4c79..a1e0439e 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 f05705a2..6d1b6dcf 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 00000000..a0a65651 --- /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 3178c63a..bc8c84f1 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 93af9d75..cc2b9dc3 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 00000000..6302477a --- /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 074/154] 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 925860ac..5a5429e6 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 075/154] 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 5a5429e6..d001aaa5 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 076/154] Makefile: Switch to latest version of staticcheck --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e1f5008c..48a693aa 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 077/154] Makefile: Add commands for testing coverage --- .gitignore | 5 ++++- Makefile | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e2006062..9d26f476 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 48a693aa..2ff82e3c 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 078/154] 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 d001aaa5..108a5e9a 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 079/154] Updated changelog with latest changes --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d935fe..a6f10d95 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 080/154] 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 fb2f3390..2661ac01 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 081/154] 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 a6f10d95..4f84c077 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 108a5e9a..c30f5649 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 082/154] 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 4c4dd778..925860ac 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 083/154] 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 925860ac..5a5429e6 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 084/154] 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 5a5429e6..d001aaa5 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 085/154] Makefile: Switch to latest version of staticcheck --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e1f5008c..48a693aa 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 086/154] Makefile: Add commands for testing coverage --- .gitignore | 5 ++++- Makefile | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e2006062..9d26f476 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 48a693aa..2ff82e3c 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 087/154] 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 d001aaa5..108a5e9a 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 088/154] Updated changelog with latest changes --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d935fe..a6f10d95 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 089/154] 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 fb2f3390..2661ac01 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 090/154] 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 a6f10d95..4f84c077 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 108a5e9a..c30f5649 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 091/154] 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 a1e0439e..7a33fa08 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 092/154] 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 6d1b6dcf..91942a36 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 093/154] 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 02c343d6..9a9f7f76 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 094/154] 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 a0a65651..364b7c40 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 095/154] 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 7a33fa08..5918f7cd 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 096/154] 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 91942a36..af7aab80 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 097/154] 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 bc8c84f1..75534592 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 098/154] 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 cc2b9dc3..be27db3a 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 099/154] 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 4f84c077..335aaf36 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 100/154] CHANGELOG: Removed git conflict marker --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a631f144..335aaf36 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 101/154] 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 4c4dd778..925860ac 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 102/154] 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 925860ac..5a5429e6 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 103/154] 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 5a5429e6..d001aaa5 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 104/154] Makefile: Switch to latest version of staticcheck --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e1f5008c..48a693aa 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 105/154] Makefile: Add commands for testing coverage --- .gitignore | 5 ++++- Makefile | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e2006062..9d26f476 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 48a693aa..2ff82e3c 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 106/154] 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 d001aaa5..108a5e9a 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 107/154] Updated changelog with latest changes --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d935fe..a6f10d95 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 108/154] 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 fb2f3390..2661ac01 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 109/154] 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 a6f10d95..4f84c077 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 108a5e9a..c30f5649 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 110/154] :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 00000000..02c343d6 --- /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 74dc4c79..a1e0439e 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 f05705a2..6d1b6dcf 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 00000000..a0a65651 --- /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 3178c63a..bc8c84f1 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 93af9d75..cc2b9dc3 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 00000000..6302477a --- /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 111/154] 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 c30f5649..ce1c78c1 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 112/154] Updated changelog with latest changes --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f84c077..c87682b3 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 113/154] 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 a1e0439e..7a33fa08 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 114/154] 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 6d1b6dcf..91942a36 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 115/154] 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 02c343d6..9a9f7f76 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 116/154] 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 a0a65651..364b7c40 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 117/154] 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 7a33fa08..5918f7cd 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 118/154] 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 91942a36..af7aab80 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 119/154] 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 bc8c84f1..75534592 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 120/154] 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 cc2b9dc3..be27db3a 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 121/154] 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 c87682b3..335aaf36 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 122/154] 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 27bc8c46..96eeee10 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 123/154] 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 88bdc207..36b6aa31 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 124/154] 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 335aaf36..0f5c8d53 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 125/154] 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 9ff3f903..00000000 --- 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 126/154] 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 2661ac01..fe0fbb08 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 d2403151..1f42f55b 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 127/154] Fix #579: Update changelog --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f5c8d53..1c6ab247 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 128/154] 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 1c6ab247..211ddbbe 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 ce1c78c1..d11bfb57 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 fe0fbb08..00000000 --- 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 1f42f55b..00000000 --- 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 b0e3c4c1..fb8a1cbc 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 421705eb..0ef1c854 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 00000000..bafa6a03 --- /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 b04888a6..00000000 --- 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 129/154] 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 4ece27fc..dc6a3979 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 ecfdd7cf..df175331 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 22f531fc..4c1b0354 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 1fc5de59..b9ec26b4 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 56ebebbe..d3a11981 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 130/154] 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 bafa6a03..ab372846 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 131/154] 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 26565b54..00000000 --- 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 1ce43408..00000000 --- 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 e730209b..00000000 --- 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 47cf5e49..00000000 --- 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 f473a93e..e0b9bfb3 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 9f4869ce..61d4b81a 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 132/154] Updated CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 211ddbbe..fafcabf2 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 133/154] 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 992a378d..7ae4fd3c 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 ea97461b..bac214c2 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 d1adc3e4..07fecbc4 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 93cb030b..f25f49f6 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 65035b28..69bfab8f 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 6bd765f4..0694b130 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 e25c522c..c0ed0782 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 c228f06c..f50e5430 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 3e6af1ee..05884597 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 70db2f27..11e040d2 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 51a1ee10..c8c3f316 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 b89e17fe..fe24a415 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 3fa6d490..add12bf9 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 c6a1e80a..30f8d030 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 d08fb591..57327a70 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 4ab93270..3fd18d83 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 c370c20c..dac54f7a 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 407fe406..ca4f83a7 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 fe796664..910cb361 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 5bce9e38..8fa85368 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 4c1e1af7..419d0d57 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 134/154] 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 8b92be72..3fdd1f2e 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 a1b841b8..14ca4504 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 b7c11c96..03732ee9 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 2bc2489d..ec548daa 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 d861f0ab..8876f26b 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 07a36943..82a8c951 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 a8828be8..c83402a6 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 2177363d..bf4687d2 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 a1b104c9..006ea0ab 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 c75ba9d0..aae4d2ac 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 0f5f29d2..905ed5f8 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 dd0ff7e8..20de68a2 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 cc035947..4e38da6e 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 6d127059..aac3c556 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 0dc007af..cfca9482 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 08c6dc67..1ee873a8 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 f3da1d99..239bedfd 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 145f0e9d..1553f6b3 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 3d9a92c1..ff2f0918 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 b6205ded..bcbe30aa 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 69e4becd..2a5418e3 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 135/154] 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 bac214c2..66eb6041 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 780cfdb5..9586e0b3 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 00000000..cda055d8 --- /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 136/154] 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 9586e0b3..4bba0365 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 137/154] 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 fafcabf2..6a8deeb3 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 66eb6041..61b25b9b 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 4bba0365..3c8305a1 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 00000000..5f40a61b --- /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 00000000..187cf405 --- /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 138/154] 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 ab372846..bafa6a03 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 8fa85368..3453a72e 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 192688ed..7e5fdb26 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 139/154] 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 099c424a..c37978a8 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 c6f9fe8f..1aa13058 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 45032895..6074774b 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 6fa6f23c..2c5d21f4 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 046df3d8..d3c877cf 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 d003e315..fdc58044 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 506f1a7a..20f7e8db 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 67801ad9..dbc02de5 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 c433a686..c217ecf1 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 894876b5..44e3e122 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 f89b46e6..ae8ed4f1 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 ace1aa9d..1b731ab2 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 91edd49e..70087814 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 e997a079..9d17757a 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 ce33192c..378ec5be 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 8762bdd6..f8760359 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 4b995759..cc0eaf2f 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 255d59b9..90594902 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 140/154] 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 6a8deeb3..ede17977 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 141/154] 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 d11bfb57..f669301e 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 00000000..13054e30 --- /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 142/154] 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 13054e30..bb351ded 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 143/154] 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 7ae4fd3c..264779bb 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 25768fff..5e36776b 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 144/154] 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 264779bb..1505831d 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 508ca35b..ad57cb82 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 29bdb230..5cb0a700 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 145/154] 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 ede17977..d9e4e3c7 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 0694b130..38736fae 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 596ac61e..e37f79e5 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 146/154] 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 c0ed0782..9bb3ebfb 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 147/154] 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 c83402a6..c9339948 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 148/154] 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 9bb3ebfb..e7db3c73 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 c9339948..d1bca7aa 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 149/154] 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 f669301e..04a91948 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 150/154] 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 81e16725..cdd49093 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 151/154] 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 04a91948..5f091552 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 152/154] 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 b76d7e6f..721c974a 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 6dcc5c06..9e2326bf 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 153/154] 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 b76d7e6f..9cd6ab8e 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 6dcc5c06..4f2dc8ef 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 154/154] 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 83f62324..d433f2dc 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"