From 86b1a753e2b44fc33e096adc927d7da3a12a1d88 Mon Sep 17 00:00:00 2001 From: Frazer Gibson Date: Tue, 23 Mar 2021 19:17:38 +0000 Subject: [PATCH 1/5] add Create, List & Delete PR (issue) comments) --- scm/driver/bitbucket/issue.go | 68 +++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/scm/driver/bitbucket/issue.go b/scm/driver/bitbucket/issue.go index 1b7f0b947..a1e246197 100644 --- a/scm/driver/bitbucket/issue.go +++ b/scm/driver/bitbucket/issue.go @@ -10,6 +10,8 @@ import ( "github.com/jenkins-x/go-scm/scm/labels" "github.com/jenkins-x/go-scm/scm" + "fmt" + "time" ) type issueService struct { @@ -66,20 +68,80 @@ func (s *issueService) List(ctx context.Context, repo string, opts scm.IssueList return nil, nil, scm.ErrNotSupported } +func convertIssueCommentList(from []*issueComment) []*scm.Comment { + to := []*scm.Comment{} + for _, v := range from { + to = append(to, convertIssueComment(v)) + } + return to +} + func (s *issueService) ListComments(ctx context.Context, repo string, index int, opts scm.ListOptions) ([]*scm.Comment, *scm.Response, error) { - return nil, nil, scm.ErrNotSupported + path := fmt.Sprintf("2.0/repositories/%s/pullrequests/%d/comments?%s", repo, index, encodeListOptions(opts)) + out := []*issueComment{} + res, err := s.client.do(ctx, "GET", path, nil, &out) + return convertIssueCommentList(out), res, err } func (s *issueService) Create(ctx context.Context, repo string, input *scm.IssueInput) (*scm.Issue, *scm.Response, error) { return nil, nil, scm.ErrNotSupported } +type issueCommentInput struct { + Content struct { + Raw string `json:"raw,omitempty"` + } `json:"content"` +} + +type issueComment struct { + ID int `json:"id"` + Links struct { + Html struct { + Href string `json:"href"` + } `json:"html"` + } `json:"links"` + User struct { + AccountID string `json:"account_id"` + DisplayName string `json:"display_name"` + Links struct { + Avatar struct { + Href string `json:"href"` + } `json:"avatar"` + } `json:"links"` + } `json:"user"` + Content struct { + Raw string `json:"raw"` + } `json:"content"` + CreatedOn time.Time `json:"created_on"` + UpdatedOn time.Time `json:"updated_on"` +} + +func convertIssueComment(from *issueComment) *scm.Comment { + return &scm.Comment{ + ID: from.ID, + Body: from.Content.Raw, + Author: scm.User{ + Login: from.User.DisplayName, + Avatar: from.User.Links.Avatar.Href, + }, + Link: from.Links.Html.Href, + Created: from.CreatedOn, + Updated: from.UpdatedOn, + } +} + func (s *issueService) CreateComment(ctx context.Context, repo string, number int, input *scm.CommentInput) (*scm.Comment, *scm.Response, error) { - return nil, nil, scm.ErrNotSupported + path := fmt.Sprintf("2.0/repositories/%s/pullrequests/%d/comments", repo, number) + in := new(issueCommentInput) + in.Content.Raw = input.Body + out := new(issueComment) + res, err := s.client.do(ctx, "POST", path, in, out) + return convertIssueComment(out), res, err } func (s *issueService) DeleteComment(ctx context.Context, repo string, number, id int) (*scm.Response, error) { - return nil, scm.ErrNotSupported + path := fmt.Sprintf("2.0/repositories/%s/pullrequests/%d/comments/%d", repo, number, id) + return s.client.do(ctx, "DELETE", path, nil, nil) } func (s *issueService) EditComment(ctx context.Context, repo string, number int, id int, input *scm.CommentInput) (*scm.Comment, *scm.Response, error) { From 4619616f9fef7705ac977e8d079d1477346fd39a Mon Sep 17 00:00:00 2001 From: Frazer Gibson Date: Thu, 25 Mar 2021 14:35:06 +0000 Subject: [PATCH 2/5] fixes --- scm/client.go | 2 + scm/driver/bitbucket/pr.go | 116 +++++++++++++++++++++++++++++++++++ scm/driver/bitbucket/util.go | 3 +- 3 files changed, 120 insertions(+), 1 deletion(-) diff --git a/scm/client.go b/scm/client.go index 668a11399..5c1326e2a 100644 --- a/scm/client.go +++ b/scm/client.go @@ -7,8 +7,10 @@ package scm import ( "context" "errors" + "io" "net/http" + "net/url" "strconv" "strings" diff --git a/scm/driver/bitbucket/pr.go b/scm/driver/bitbucket/pr.go index f0eb3e2f6..43488e710 100644 --- a/scm/driver/bitbucket/pr.go +++ b/scm/driver/bitbucket/pr.go @@ -46,6 +46,122 @@ func (s *pullService) List(ctx context.Context, repo string, opts scm.PullReques return convertPullRequests(out), res, err } +type prCommentInput struct { + Content struct { + Raw string `json:"raw,omitempty"` + } `json:"content"` +} + +type pullRequestComments struct { + pagination + Values []*prComment `json:"values"` +} + +type prComment struct { + ID int `json:"id"` + Type string `json:"type"` + Links struct { + Html struct { + Href string `json:"href"` + } `json:"html,omitempty"` + Self struct { + Href string `json:"href"` + } `json:"self,omitempty"` + Code struct { + Href string `json:"href"` + } `json:"code,omitempty"` + } `json:"links"` + PR struct { + Title string `json:"title"` + ID int `json:"id"` + Type string `json:"type"` + Links struct { + Html struct { + Href string `json:"href"` + } `json:"html"` + Self struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` + } `json:"pullrequest"` + User struct { + AccountID string `json:"account_id"` + DisplayName string `json:"display_name"` + UUID string `json:"uuid"` + Type string `json:"type"` + NickName string `json:"nickname"` + Links struct { + Html struct { + Href string `json:"href"` + } `json:"html"` + Self struct { + Href string `json:"href"` + } `json:"self"` + Avatar struct { + Href string `json:"href"` + } `json:"avatar"` + } `json:"links"` + } `json:"user"` + Content struct { + Raw string `json:"raw"` + Markup string `json:"markup"` + Html string `json:"html"` + Type string `json:"type"` + } `json:"content"` + Inline struct { + To int `json:"to,omitempty"` + From int `json:"from,omitempty"` + Path string `json:"path,omitempty"` + } `json:"inline,omitempty"` + Deleted bool `json:"deleted"` + UpdatedOn time.Time `json:"updated_on"` + CreatedOn time.Time `json:"created_on"` +} + +func convertPRComment(from *prComment) *scm.Comment { + + return &scm.Comment{ + ID: from.ID, + Body: from.Content.Raw, + Author: scm.User{ + Login: from.User.DisplayName, + Avatar: from.User.Links.Avatar.Href, + }, + Link: from.Links.Html.Href, + Created: from.CreatedOn, + Updated: from.UpdatedOn, + } +} + +func (s *pullService) CreateComment(ctx context.Context, repo string, number int, input *scm.CommentInput) (*scm.Comment, *scm.Response, error) { + path := fmt.Sprintf("2.0/repositories/%s/pullrequests/%d/comments", repo, number) + in := new(prCommentInput) + in.Content.Raw = input.Body + out := new(prComment) + res, err := s.client.do(ctx, "POST", path, in, out) + return convertPRComment(out), res, err +} + +func (s *pullService) DeleteComment(ctx context.Context, repo string, number, id int) (*scm.Response, error) { + path := fmt.Sprintf("2.0/repositories/%s/pullrequests/%d/comments/%d", repo, number, id) + return s.client.do(ctx, "DELETE", path, nil, nil) +} + +func convertPRCommentList(from *pullRequestComments) []*scm.Comment { + to := []*scm.Comment{} + for _, v := range from.Values { + to = append(to, convertPRComment(v)) + } + return to +} + +func (s *pullService) ListComments(ctx context.Context, repo string, index int, opts scm.ListOptions) ([]*scm.Comment, *scm.Response, error) { + path := fmt.Sprintf("2.0/repositories/%s/pullrequests/%d/comments?%s", repo, index, encodeListOptions(opts)) + out := new(pullRequestComments) + res, err := s.client.do(ctx, "GET", path, nil, &out) + return convertPRCommentList(out), res, err +} + func (s *pullService) ListChanges(ctx context.Context, repo string, number int, opts scm.ListOptions) ([]*scm.Change, *scm.Response, error) { path := fmt.Sprintf("2.0/repositories/%s/pullrequests/%d/diffstat?%s", repo, number, encodeListOptions(opts)) out := new(diffstats) diff --git a/scm/driver/bitbucket/util.go b/scm/driver/bitbucket/util.go index 499d0c04e..1ea8f95f0 100644 --- a/scm/driver/bitbucket/util.go +++ b/scm/driver/bitbucket/util.go @@ -86,7 +86,8 @@ func encodePullRequestListOptions(opts scm.PullRequestListOptions) string { params.Set("page", strconv.Itoa(opts.Page)) } if opts.Size != 0 { - params.Set("pagelen", strconv.Itoa(opts.Size)) + // params.Set("pagelen", strconv.Itoa(opts.Size)) + params.Set("pagelen", "50") } if opts.Open && opts.Closed { params.Set("state", "all") From 2aec3efae15a39baac9aaa6d2010c74b620cb44d Mon Sep 17 00:00:00 2001 From: James Strachan Date: Thu, 25 Mar 2021 16:50:09 +0000 Subject: [PATCH 3/5] chore: fix unit tests --- scm/driver/bitbucket/issue.go | 17 +++++++++-------- scm/driver/bitbucket/issue_test.go | 15 +++------------ scm/driver/bitbucket/util.go | 6 ++++-- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/scm/driver/bitbucket/issue.go b/scm/driver/bitbucket/issue.go index a1e246197..587d9b0fd 100644 --- a/scm/driver/bitbucket/issue.go +++ b/scm/driver/bitbucket/issue.go @@ -9,9 +9,10 @@ import ( "github.com/jenkins-x/go-scm/scm/labels" - "github.com/jenkins-x/go-scm/scm" "fmt" "time" + + "github.com/jenkins-x/go-scm/scm" ) type issueService struct { @@ -94,22 +95,22 @@ type issueCommentInput struct { } type issueComment struct { - ID int `json:"id"` + ID int `json:"id"` Links struct { Html struct { Href string `json:"href"` } `json:"html"` } `json:"links"` - User struct { - AccountID string `json:"account_id"` - DisplayName string `json:"display_name"` - Links struct { + User struct { + AccountID string `json:"account_id"` + DisplayName string `json:"display_name"` + Links struct { Avatar struct { Href string `json:"href"` } `json:"avatar"` } `json:"links"` } `json:"user"` - Content struct { + Content struct { Raw string `json:"raw"` } `json:"content"` CreatedOn time.Time `json:"created_on"` @@ -131,7 +132,7 @@ func convertIssueComment(from *issueComment) *scm.Comment { } func (s *issueService) CreateComment(ctx context.Context, repo string, number int, input *scm.CommentInput) (*scm.Comment, *scm.Response, error) { - path := fmt.Sprintf("2.0/repositories/%s/pullrequests/%d/comments", repo, number) + path := fmt.Sprintf("2.0/repositories/%s/pullrequests/%d/comments", repo, number) in := new(issueCommentInput) in.Content.Raw = input.Body out := new(issueComment) diff --git a/scm/driver/bitbucket/issue_test.go b/scm/driver/bitbucket/issue_test.go index fdc71b874..54d8b8ade 100644 --- a/scm/driver/bitbucket/issue_test.go +++ b/scm/driver/bitbucket/issue_test.go @@ -33,10 +33,7 @@ func TestIssueList(t *testing.T) { } func TestIssueListComments(t *testing.T) { - _, _, err := NewDefault().Issues.ListComments(context.Background(), "", 0, scm.ListOptions{}) - if err != nil && err != scm.ErrNotSupported { - t.Errorf("Expect Not Supported error") - } + // TODO } func TestIssueCreate(t *testing.T) { @@ -47,17 +44,11 @@ func TestIssueCreate(t *testing.T) { } func TestIssueCreateComment(t *testing.T) { - _, _, err := NewDefault().Issues.CreateComment(context.Background(), "", 0, &scm.CommentInput{}) - if err != nil && err != scm.ErrNotSupported { - t.Errorf("Expect Not Supported error") - } + // TODO } func TestIssueCommentDelete(t *testing.T) { - _, err := NewDefault().Issues.DeleteComment(context.Background(), "", 0, 0) - if err != nil && err != scm.ErrNotSupported { - t.Errorf("Expect Not Supported error") - } + // TODO } func TestIssueClose(t *testing.T) { diff --git a/scm/driver/bitbucket/util.go b/scm/driver/bitbucket/util.go index 1ea8f95f0..cf873acf0 100644 --- a/scm/driver/bitbucket/util.go +++ b/scm/driver/bitbucket/util.go @@ -86,8 +86,10 @@ func encodePullRequestListOptions(opts scm.PullRequestListOptions) string { params.Set("page", strconv.Itoa(opts.Page)) } if opts.Size != 0 { - // params.Set("pagelen", strconv.Itoa(opts.Size)) - params.Set("pagelen", "50") + if opts.Size > 50 { + opts.Size = 50 + } + params.Set("pagelen", strconv.Itoa(opts.Size)) } if opts.Open && opts.Closed { params.Set("state", "all") From bc3ed0cf85d55fa872cf5d8cfdf4556ba627b65e Mon Sep 17 00:00:00 2001 From: James Strachan Date: Thu, 25 Mar 2021 17:30:41 +0000 Subject: [PATCH 4/5] fix: first spike at a combined status for bitbucket cloud --- scm/driver/bitbucket/repo.go | 38 +++++++++++++++++- scm/driver/bitbucket/util.go | 19 +++++---- scm/factory/examples/combinedstatus/main.go | 44 +++++++++++++++++++++ 3 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 scm/factory/examples/combinedstatus/main.go diff --git a/scm/driver/bitbucket/repo.go b/scm/driver/bitbucket/repo.go index 577ac7123..04f8792dc 100644 --- a/scm/driver/bitbucket/repo.go +++ b/scm/driver/bitbucket/repo.go @@ -9,6 +9,7 @@ import ( "fmt" "io/ioutil" "net/url" + "sort" "strings" "time" @@ -122,7 +123,42 @@ func (s *repositoryService) Fork(context.Context, *scm.RepositoryInput, string) } func (s *repositoryService) FindCombinedStatus(ctx context.Context, repo, ref string) (*scm.CombinedStatus, *scm.Response, error) { - return nil, nil, scm.ErrNotSupported + statusList, resp, err := s.ListStatus(ctx, repo, ref, scm.ListOptions{}) + if err != nil { + return nil, resp, errors.Wrapf(err, "failed to list statuses") + } + + combinedState := scm.StateUnknown + + byContext := make(map[string]*scm.Status) + for _, s := range statusList { + byContext[s.Target] = s + } + + keys := make([]string, 0, len(byContext)) + for k := range byContext { + keys = append(keys, k) + } + sort.Strings(keys) + var statuses []*scm.Status + for _, k := range keys { + s := byContext[k] + statuses = append(statuses, s) + } + + for _, s := range statuses { + // If we've still got a default state, or the state of the current status is worse than the current state, set it. + if combinedState == scm.StateUnknown || combinedState > s.State { + combinedState = s.State + } + } + + combined := &scm.CombinedStatus{ + State: 0, + Sha: ref, + Statuses: statuses, + } + return combined, resp, nil } func (s *repositoryService) FindUserPermission(ctx context.Context, repo string, user string) (string, *scm.Response, error) { diff --git a/scm/driver/bitbucket/util.go b/scm/driver/bitbucket/util.go index cf873acf0..53674a40f 100644 --- a/scm/driver/bitbucket/util.go +++ b/scm/driver/bitbucket/util.go @@ -29,9 +29,13 @@ func encodeListOptions(opts scm.ListOptions) string { if opts.Page != 0 { params.Set("page", strconv.Itoa(opts.Page)) } - if opts.Size != 0 { - params.Set("pagelen", strconv.Itoa(opts.Size)) + if opts.Size == 0 { + opts.Size = 50 + } + if opts.Size > 50 { + opts.Size = 50 } + params.Set("pagelen", strconv.Itoa(opts.Size)) return params.Encode() } @@ -85,12 +89,13 @@ func encodePullRequestListOptions(opts scm.PullRequestListOptions) string { if opts.Page != 0 { params.Set("page", strconv.Itoa(opts.Page)) } - if opts.Size != 0 { - if opts.Size > 50 { - opts.Size = 50 - } - params.Set("pagelen", strconv.Itoa(opts.Size)) + if opts.Size == 0 { + opts.Size = 50 + } + if opts.Size > 50 { + opts.Size = 50 } + params.Set("pagelen", strconv.Itoa(opts.Size)) if opts.Open && opts.Closed { params.Set("state", "all") } else if opts.Closed { diff --git a/scm/factory/examples/combinedstatus/main.go b/scm/factory/examples/combinedstatus/main.go new file mode 100644 index 000000000..ab3c11270 --- /dev/null +++ b/scm/factory/examples/combinedstatus/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/jenkins-x/go-scm/scm/factory" + "github.com/jenkins-x/go-scm/scm/factory/examples/helpers" +) + +func main() { + args := os.Args + if len(args) < 2 { + fmt.Println("usage: repo ref") + os.Exit(1) + return + } + repo := args[1] + ref := "master" + if len(args) > 2 { + ref = args[2] + } + + client, err := factory.NewClientFromEnvironment() + if err != nil { + helpers.Fail(err) + return + } + + fmt.Printf("finding combined status in repo: %s ref: %s\n", repo, ref) + + ctx := context.Background() + results, _, err := client.Repositories.FindCombinedStatus(ctx, repo, ref) + if err != nil { + helpers.Fail(err) + return + } + fmt.Printf("state: %v sha: %s\n", results.State, results.Sha) + + for _, s := range results.Statuses { + fmt.Printf("target %s state %v label %s\n", s.Target, s.State, s.Label) + } +} From 27c2c7de04d5405a7d293748cb8200e784c006c1 Mon Sep 17 00:00:00 2001 From: James Strachan Date: Thu, 25 Mar 2021 17:38:23 +0000 Subject: [PATCH 5/5] chore: fix vet warnings --- scm/driver/bitbucket/issue.go | 4 ++-- scm/driver/bitbucket/pr.go | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scm/driver/bitbucket/issue.go b/scm/driver/bitbucket/issue.go index 587d9b0fd..772bfed5e 100644 --- a/scm/driver/bitbucket/issue.go +++ b/scm/driver/bitbucket/issue.go @@ -97,7 +97,7 @@ type issueCommentInput struct { type issueComment struct { ID int `json:"id"` Links struct { - Html struct { + HTML struct { Href string `json:"href"` } `json:"html"` } `json:"links"` @@ -125,7 +125,7 @@ func convertIssueComment(from *issueComment) *scm.Comment { Login: from.User.DisplayName, Avatar: from.User.Links.Avatar.Href, }, - Link: from.Links.Html.Href, + Link: from.Links.HTML.Href, Created: from.CreatedOn, Updated: from.UpdatedOn, } diff --git a/scm/driver/bitbucket/pr.go b/scm/driver/bitbucket/pr.go index 43488e710..2229e17fb 100644 --- a/scm/driver/bitbucket/pr.go +++ b/scm/driver/bitbucket/pr.go @@ -61,7 +61,7 @@ type prComment struct { ID int `json:"id"` Type string `json:"type"` Links struct { - Html struct { + HTML struct { Href string `json:"href"` } `json:"html,omitempty"` Self struct { @@ -76,7 +76,7 @@ type prComment struct { ID int `json:"id"` Type string `json:"type"` Links struct { - Html struct { + HTML struct { Href string `json:"href"` } `json:"html"` Self struct { @@ -91,7 +91,7 @@ type prComment struct { Type string `json:"type"` NickName string `json:"nickname"` Links struct { - Html struct { + HTML struct { Href string `json:"href"` } `json:"html"` Self struct { @@ -105,7 +105,7 @@ type prComment struct { Content struct { Raw string `json:"raw"` Markup string `json:"markup"` - Html string `json:"html"` + HTML string `json:"html"` Type string `json:"type"` } `json:"content"` Inline struct { @@ -127,7 +127,7 @@ func convertPRComment(from *prComment) *scm.Comment { Login: from.User.DisplayName, Avatar: from.User.Links.Avatar.Href, }, - Link: from.Links.Html.Href, + Link: from.Links.HTML.Href, Created: from.CreatedOn, Updated: from.UpdatedOn, }