Skip to content

Commit 567c839

Browse files
authored
Add support for issues (#134)
* issues: Add simple Gets function for issues (#133) * issues: Add buildIssueBody and Create (#133) * issues: Add support for Get (#133) * issues: add Delete and Update (#133) * issues: add Vote methods (#133) * issues: add watch methods (#133) * issues: move json.Marshal to the correct location (#133) * issues: add comment methods (#133) * issues: add interface to bitbucket.go (#133) * issues: add Issues to injectClient (#133) * issues: bugfix - add missing slash (#133) * issues: bugfix - fix method (#133) * issues: bugfix - add empty check for title (#133) * issues: bugfix - fix error check (#133) * issues: add filtering and sorting to comments (#133) * issues: add api for changes (#133)
1 parent b8bc8c9 commit 567c839

File tree

4 files changed

+357
-0
lines changed

4 files changed

+357
-0
lines changed

bitbucket.go

+60
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,28 @@ type workspace interface {
3030
CreateProject(opt ProjectOptions) (*Project, error)
3131
}
3232

33+
type issues interface {
34+
Gets(io *IssuesOptions) (interface{}, error)
35+
Get(io *IssuesOptions) (interface{}, error)
36+
Delete(io *IssuesOptions) (interface{}, error)
37+
Update(io *IssuesOptions) (interface{}, error)
38+
Create(io *IssuesOptions) (interface{}, error)
39+
GetVote(io *IssuesOptions) (bool, interface{}, error)
40+
PutVote(io *IssuesOptions) error
41+
DeleteVote(io *IssuesOptions) error
42+
GetWatch(io *IssuesOptions) (bool, interface{}, error)
43+
PutWatch(io *IssuesOptions) error
44+
DeleteWatch(io *IssuesOptions) error
45+
GetComments(ico *IssueCommentsOptions) (interface{}, error)
46+
CreateComment(ico *IssueCommentsOptions) (interface{}, error)
47+
GetComment(ico *IssueCommentsOptions) (interface{}, error)
48+
UpdateComment(ico *IssueCommentsOptions) (interface{}, error)
49+
DeleteComment(ico *IssueCommentsOptions) (interface{}, error)
50+
GetChanges(ico *IssueChangesOptions) (interface{}, error)
51+
CreateChange(ico *IssueChangesOptions) (interface{}, error)
52+
GetChange(ico *IssueChangesOptions) (interface{}, error)
53+
}
54+
3355
type repository interface {
3456
Get(opt RepositoryOptions) (*Repository, error)
3557
Create(opt RepositoryOptions) (*Repository, error)
@@ -228,6 +250,44 @@ type PullRequestsOptions struct {
228250
Sort string `json:"sort"`
229251
}
230252

253+
type IssuesOptions struct {
254+
ID string `json:"id"`
255+
Owner string `json:"owner"`
256+
RepoSlug string `json:"repo_slug"`
257+
States []string `json:"states"`
258+
Query string `json:"query"`
259+
Sort string `json:"sort"`
260+
Title string `json:"title"`
261+
Content string `json:"content"`
262+
State string `json:"state"`
263+
Kind string `json:"kind"`
264+
Milestone string `json:"milestone"`
265+
Component string `json:"component"`
266+
Priority string `json:"priority"`
267+
Version string `json:"version"`
268+
Assignee string `json:"assignee"`
269+
}
270+
271+
type IssueCommentsOptions struct {
272+
IssuesOptions
273+
Query string `json:"query"`
274+
Sort string `json:"sort"`
275+
CommentContent string `json:"comment_content"`
276+
CommentID string `json:"comment_id"`
277+
}
278+
279+
type IssueChangesOptions struct {
280+
IssuesOptions
281+
Query string `json:"query"`
282+
Sort string `json:"sort"`
283+
Message string `json:"message"`
284+
ChangeID string `json:"change_id"`
285+
Changes []struct {
286+
Type string
287+
NewValue string
288+
} `json:"changes"`
289+
}
290+
231291
type CommitsOptions struct {
232292
Owner string `json:"owner"`
233293
RepoSlug string `json:"repo_slug"`

client.go

+1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ func injectClient(a *auth) *Client {
141141
PullRequests: &PullRequests{c: c},
142142
Pipelines: &Pipelines{c: c},
143143
Repository: &Repository{c: c},
144+
Issues: &Issues{c: c},
144145
Commits: &Commits{c: c},
145146
Diff: &Diff{c: c},
146147
BranchRestrictions: &BranchRestrictions{c: c},

issues.go

+295
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
package bitbucket
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/url"
7+
"os"
8+
"strings"
9+
10+
"github.com/k0kubun/pp"
11+
)
12+
13+
type Issues struct {
14+
c *Client
15+
}
16+
17+
func (p *Issues) Gets(io *IssuesOptions) (interface{}, error) {
18+
url, err := url.Parse(p.c.GetApiBaseURL() + "/repositories/" + io.Owner + "/" + io.RepoSlug + "/issues/")
19+
if err != nil {
20+
return nil, err
21+
}
22+
23+
if io.States != nil && len(io.States) != 0 {
24+
query := url.Query()
25+
for _, state := range io.States {
26+
query.Set("state", state)
27+
}
28+
url.RawQuery = query.Encode()
29+
}
30+
31+
if io.Query != "" {
32+
query := url.Query()
33+
query.Set("q", io.Query)
34+
url.RawQuery = query.Encode()
35+
}
36+
37+
if io.Sort != "" {
38+
query := url.Query()
39+
query.Set("sort", io.Sort)
40+
url.RawQuery = query.Encode()
41+
}
42+
43+
return p.c.execute("GET", url.String(), "")
44+
}
45+
46+
func (p *Issues) Get(io *IssuesOptions) (interface{}, error) {
47+
urlStr := p.c.GetApiBaseURL() + "/repositories/" + io.Owner + "/" + io.RepoSlug + "/issues/" + io.ID
48+
return p.c.execute("GET", urlStr, "")
49+
}
50+
51+
func (p *Issues) Delete(io *IssuesOptions) (interface{}, error) {
52+
urlStr := p.c.GetApiBaseURL() + "/repositories/" + io.Owner + "/" + io.RepoSlug + "/issues/" + io.ID
53+
return p.c.execute("DELETE", urlStr, "")
54+
}
55+
56+
func (p *Issues) Update(io *IssuesOptions) (interface{}, error) {
57+
data := p.buildIssueBody(io)
58+
urlStr := p.c.requestUrl("/repositories/%s/%s/issues/%s", io.Owner, io.RepoSlug, io.ID)
59+
return p.c.execute("PUT", urlStr, data)
60+
}
61+
62+
func (p *Issues) Create(io *IssuesOptions) (interface{}, error) {
63+
data := p.buildIssueBody(io)
64+
urlStr := p.c.requestUrl("/repositories/%s/%s/issues", io.Owner, io.RepoSlug)
65+
return p.c.execute("POST", urlStr, data)
66+
}
67+
68+
func (p *Issues) GetVote(io *IssuesOptions) (bool, interface{}, error) {
69+
// A 404 indicates that the user hasn't voted
70+
urlStr := p.c.GetApiBaseURL() + "/repositories/" + io.Owner + "/" + io.RepoSlug + "/issues/" + io.ID + "/vote"
71+
data, err := p.c.execute("GET", urlStr, "")
72+
if err != nil && strings.HasPrefix(err.Error(), "404") {
73+
return false, data, nil
74+
}
75+
return true, nil, err
76+
}
77+
78+
func (p *Issues) PutVote(io *IssuesOptions) error {
79+
urlStr := p.c.GetApiBaseURL() + "/repositories/" + io.Owner + "/" + io.RepoSlug + "/issues/" + io.ID + "/vote"
80+
_, err := p.c.execute("PUT", urlStr, "")
81+
return err
82+
}
83+
84+
func (p *Issues) DeleteVote(io *IssuesOptions) error {
85+
urlStr := p.c.GetApiBaseURL() + "/repositories/" + io.Owner + "/" + io.RepoSlug + "/issues/" + io.ID + "/vote"
86+
_, err := p.c.execute("DELETE", urlStr, "")
87+
return err
88+
}
89+
90+
func (p *Issues) GetWatch(io *IssuesOptions) (bool, interface{}, error) {
91+
// A 404 indicates that the user hasn't watchd
92+
urlStr := p.c.GetApiBaseURL() + "/repositories/" + io.Owner + "/" + io.RepoSlug + "/issues/" + io.ID + "/watch"
93+
data, err := p.c.execute("GET", urlStr, "")
94+
if err != nil && strings.HasPrefix(err.Error(), "404") {
95+
return false, data, nil
96+
}
97+
return true, nil, err
98+
}
99+
100+
func (p *Issues) PutWatch(io *IssuesOptions) error {
101+
urlStr := p.c.GetApiBaseURL() + "/repositories/" + io.Owner + "/" + io.RepoSlug + "/issues/" + io.ID + "/watch"
102+
_, err := p.c.execute("PUT", urlStr, "")
103+
return err
104+
}
105+
106+
func (p *Issues) DeleteWatch(io *IssuesOptions) error {
107+
urlStr := p.c.GetApiBaseURL() + "/repositories/" + io.Owner + "/" + io.RepoSlug + "/issues/" + io.ID + "/watch"
108+
_, err := p.c.execute("DELETE", urlStr, "")
109+
return err
110+
}
111+
112+
func (p *Issues) buildIssueBody(io *IssuesOptions) string {
113+
body := map[string]interface{}{}
114+
115+
// This feld is required
116+
if io.Title != "" {
117+
body["title"] = io.Title
118+
}
119+
120+
if io.Content != "" {
121+
body["content"] = map[string]interface{}{
122+
"raw": io.Content,
123+
}
124+
}
125+
126+
if io.State != "" {
127+
body["state"] = io.State
128+
}
129+
130+
if io.Kind != "" {
131+
body["kind"] = io.Kind
132+
}
133+
134+
if io.Priority != "" {
135+
body["priority"] = io.Priority
136+
}
137+
138+
if io.Milestone != "" {
139+
body["milestone"] = map[string]interface{}{
140+
"name": io.Milestone,
141+
}
142+
}
143+
144+
if io.Component != "" {
145+
body["component"] = map[string]interface{}{
146+
"name": io.Component,
147+
}
148+
}
149+
150+
if io.Version != "" {
151+
body["version"] = map[string]interface{}{
152+
"name": io.Component,
153+
}
154+
}
155+
if io.Assignee != "" {
156+
body["assignee"] = map[string]interface{}{
157+
"uuid": io.Assignee,
158+
}
159+
}
160+
161+
data, err := json.Marshal(body)
162+
if err != nil {
163+
pp.Println(err)
164+
os.Exit(9)
165+
}
166+
167+
return string(data)
168+
}
169+
170+
func (p *Issues) GetComments(ico *IssueCommentsOptions) (interface{}, error) {
171+
url, err := url.Parse(p.c.GetApiBaseURL() + "/repositories/" + ico.Owner + "/" + ico.RepoSlug + "/issues/" + ico.ID + "/comments")
172+
if err != nil {
173+
return nil, err
174+
}
175+
176+
if ico.Query != "" {
177+
query := url.Query()
178+
query.Set("q", ico.Query)
179+
url.RawQuery = query.Encode()
180+
}
181+
182+
if ico.Sort != "" {
183+
query := url.Query()
184+
query.Set("sort", ico.Sort)
185+
url.RawQuery = query.Encode()
186+
}
187+
return p.c.execute("GET", url.String(), "")
188+
}
189+
190+
func (p *Issues) CreateComment(ico *IssueCommentsOptions) (interface{}, error) {
191+
urlStr := p.c.requestUrl("/repositories/%s/%s/issues/%s/comments", ico.Owner, ico.RepoSlug, ico.ID)
192+
// as the body/map only takes a single value, I do not think it's useful to create a seperate method here
193+
194+
data, err := p.buildCommentBody(ico)
195+
if err != nil {
196+
return nil, err
197+
}
198+
199+
return p.c.execute("POST", urlStr, data)
200+
}
201+
202+
func (p *Issues) GetComment(ico *IssueCommentsOptions) (interface{}, error) {
203+
urlStr := p.c.GetApiBaseURL() + "/repositories/" + ico.Owner + "/" + ico.RepoSlug + "/issues/" + ico.ID + "/comments/" + ico.CommentID
204+
return p.c.execute("GET", urlStr, "")
205+
}
206+
207+
func (p *Issues) UpdateComment(ico *IssueCommentsOptions) (interface{}, error) {
208+
urlStr := p.c.requestUrl("/repositories/%s/%s/issues/%s/comments/%s", ico.Owner, ico.RepoSlug, ico.ID, ico.CommentID)
209+
// as the body/map only takes a single value, I do not think it's useful to create a seperate method here
210+
211+
data, err := p.buildCommentBody(ico)
212+
if err != nil {
213+
return nil, err
214+
}
215+
216+
return p.c.execute("PUT", urlStr, data)
217+
218+
}
219+
220+
func (p *Issues) DeleteComment(ico *IssueCommentsOptions) (interface{}, error) {
221+
urlStr := p.c.GetApiBaseURL() + "/repositories/" + ico.Owner + "/" + ico.RepoSlug + "/issues/" + ico.ID + "/comments/" + ico.CommentID
222+
return p.c.execute("DELETE", urlStr, "")
223+
}
224+
225+
func (p *Issues) buildCommentBody(ico *IssueCommentsOptions) (string, error) {
226+
body := map[string]interface{}{}
227+
body["content"] = map[string]interface{}{
228+
"raw": ico.CommentContent,
229+
}
230+
231+
data, err := json.Marshal(body)
232+
if err != nil {
233+
return "", err
234+
}
235+
return string(data), nil
236+
}
237+
238+
func (p *Issues) GetChanges(ico *IssueChangesOptions) (interface{}, error) {
239+
url, err := url.Parse(p.c.GetApiBaseURL() + "/repositories/" + ico.Owner + "/" + ico.RepoSlug + "/issues/" + ico.ID + "/changes")
240+
if err != nil {
241+
return nil, err
242+
}
243+
244+
if ico.Query != "" {
245+
query := url.Query()
246+
query.Set("q", ico.Query)
247+
url.RawQuery = query.Encode()
248+
}
249+
250+
if ico.Sort != "" {
251+
query := url.Query()
252+
query.Set("sort", ico.Sort)
253+
url.RawQuery = query.Encode()
254+
}
255+
256+
return p.c.execute("GET", url.String(), "")
257+
}
258+
259+
func (p *Issues) CreateChange(ico *IssueChangesOptions) (interface{}, error) {
260+
url, err := url.Parse(p.c.GetApiBaseURL() + "/repositories/" + ico.Owner + "/" + ico.RepoSlug + "/issues/" + ico.ID + "/changes")
261+
if err != nil {
262+
return nil, err
263+
}
264+
265+
body := map[string]interface{}{}
266+
if ico.Message != "" {
267+
body["message"] = map[string]interface{}{
268+
"raw": ico.Message,
269+
}
270+
}
271+
272+
changes := map[string]interface{}{}
273+
for _, change := range ico.Changes {
274+
changes[change.Type] = map[string]interface{}{
275+
"new": change.NewValue,
276+
}
277+
}
278+
if len(changes) > 0 {
279+
body["changes"] = changes
280+
}
281+
282+
data, err := json.Marshal(body)
283+
if err != nil {
284+
return "", err
285+
}
286+
287+
fmt.Printf("data %s", data)
288+
289+
return p.c.execute("POST", url.String(), string(data))
290+
}
291+
292+
func (p *Issues) GetChange(ico *IssueChangesOptions) (interface{}, error) {
293+
urlStr := p.c.GetApiBaseURL() + "/repositories/" + ico.Owner + "/" + ico.RepoSlug + "/issues/" + ico.ID + "/changes/" + ico.ChangeID
294+
return p.c.execute("GET", urlStr, "")
295+
}

repositories.go

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
type Repositories struct {
1212
c *Client
1313
PullRequests *PullRequests
14+
Issues *Issues
1415
Pipelines *Pipelines
1516
Repository *Repository
1617
Commits *Commits

0 commit comments

Comments
 (0)