mirror of
https://github.com/interviewstreet/go-jira.git
synced 2025-03-21 21:07:03 +02:00
feat(issues): Added support for AddWorklog and GetWorklogs
This commit is contained in:
parent
a9350ed566
commit
1ebd7e7f0d
@ -3,11 +3,11 @@ language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- "1.7.x"
|
||||
- "1.8.x"
|
||||
- "1.9.x"
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
- "1.12.x"
|
||||
|
||||
before_install:
|
||||
- go get -t ./...
|
||||
|
36
Gopkg.lock
generated
36
Gopkg.lock
generated
@ -1,36 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/fatih/structs"
|
||||
packages = ["."]
|
||||
revision = "a720dfa8df582c51dee1b36feabb906bde1588bd"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/google/go-querystring"
|
||||
packages = ["query"]
|
||||
revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/trivago/tgo"
|
||||
packages = [
|
||||
"tcontainer",
|
||||
"treflect"
|
||||
]
|
||||
revision = "e4d1ddd28c17dd89ed26327cf69fded22060671b"
|
||||
version = "v1.0.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "e84ca9eea6d233e0947b0d760913db2983fd4cbf6fd0d8690c737a71affb635c"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
46
Gopkg.toml
46
Gopkg.toml
@ -1,46 +0,0 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/fatih/structs"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/google/go-querystring"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pkg/errors"
|
||||
version = "0.8.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/trivago/tgo"
|
||||
version = "1.0.1"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
19
README.md
19
README.md
@ -17,15 +17,18 @@
|
||||
|
||||
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/).
|
||||
|
||||
## Compatible JIRA versions
|
||||
## Requirements
|
||||
|
||||
This package was tested against JIRA v6.3.4 and v7.1.2.
|
||||
* Go >= 1.8
|
||||
* JIRA v6.3.4 & v7.1.2.
|
||||
|
||||
## Installation
|
||||
|
||||
It is go gettable
|
||||
|
||||
$ go get github.com/andygrunwald/go-jira
|
||||
```bash
|
||||
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.
|
||||
|
||||
@ -40,8 +43,10 @@ import (
|
||||
|
||||
(optional) to run unit / example tests:
|
||||
|
||||
$ cd $GOPATH/src/github.com/andygrunwald/go-jira
|
||||
$ go test -v ./...
|
||||
```bash
|
||||
cd $GOPATH/src/github.com/andygrunwald/go-jira
|
||||
go test -v ./...
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
@ -239,9 +244,9 @@ If you are new to pull requests, checkout [Collaborating on projects using issue
|
||||
|
||||
### Dependency management
|
||||
|
||||
`go-jira` uses `dep` for dependency management. After cloning the repo, it's easy to make sure you have the correct dependencies by running `dep ensure`.
|
||||
`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`.
|
||||
|
||||
For adding new dependencies, updating dependencies, and other operations, the [Daily Dep](https://golang.github.io/dep/docs/daily-dep.html) is a good place to start.
|
||||
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.
|
||||
|
||||
### Sandbox environment for testing
|
||||
|
||||
|
12
go.mod
Normal file
12
go.mod
Normal file
@ -0,0 +1,12 @@
|
||||
module github.com/andygrunwald/go-jira
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/fatih/structs v1.0.0
|
||||
github.com/google/go-cmp v0.3.0
|
||||
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135
|
||||
github.com/pkg/errors v0.8.0
|
||||
github.com/trivago/tgo v1.0.1
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734
|
||||
)
|
83
issue.go
83
issue.go
@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
@ -295,6 +296,10 @@ type Parent struct {
|
||||
// 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
|
||||
|
||||
@ -394,17 +399,23 @@ type Worklog struct {
|
||||
|
||||
// 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"`
|
||||
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.
|
||||
@ -527,6 +538,22 @@ type GetQueryOptions struct {
|
||||
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"`
|
||||
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
|
||||
@ -626,7 +653,7 @@ func (s *IssueService) PostAttachment(issueID string, r io.Reader, attachmentNam
|
||||
// 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) GetWorklogs(issueID string) (*Worklog, *Response, error) {
|
||||
func (s *IssueService) GetWorklogs(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("GET", apiEndpoint, nil)
|
||||
@ -634,11 +661,34 @@ func (s *IssueService) GetWorklogs(issueID string) (*Worklog, *Response, error)
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -787,13 +837,20 @@ func (s *IssueService) DeleteComment(issueID, commentID string) error {
|
||||
// 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) AddWorklogRecord(issueID string, record *WorklogRecord) (*WorklogRecord, *Response, error) {
|
||||
func (s *IssueService) AddWorklogRecord(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("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 {
|
||||
|
132
issue_test.go
132
issue_test.go
@ -3,6 +3,7 @@ package jira
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@ -1316,31 +1317,128 @@ func TestIssueService_Delete(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func getTime(original time.Time) *Time {
|
||||
jiraTime := Time(original)
|
||||
|
||||
return &jiraTime
|
||||
}
|
||||
|
||||
func TestIssueService_GetWorklogs(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
testMux.HandleFunc("/rest/api/2/issue/10002/worklog", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testRequestURL(t, r, "/rest/api/2/issue/10002/worklog")
|
||||
|
||||
fmt.Fprint(w, `{"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"}]}`)
|
||||
})
|
||||
|
||||
worklog, _, err := testClient.Issue.GetWorklogs("10002")
|
||||
if worklog == nil {
|
||||
t.Error("Expected worklog. Worklog is nil")
|
||||
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"},
|
||||
},
|
||||
}
|
||||
|
||||
if len(worklog.Worklogs) != 1 {
|
||||
t.Error("Expected 1 worklog")
|
||||
}
|
||||
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)
|
||||
})
|
||||
|
||||
if worklog.Worklogs[0].Author.Name != "fred" {
|
||||
t.Error("Expected worklog author to be fred")
|
||||
}
|
||||
var worklog *Worklog
|
||||
var err error
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error given: %s", err)
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user