@@ -0,0 +1,198 @@
// Package gerritapi implements issues.Service using Gerrit API client.
package gerritapi
import (
"context"
"fmt"
"os"
"sort"
"strings"
"time"
"github.com/andygrunwald/go-gerrit"
"github.com/shurcooL/issues"
"github.com/shurcooL/users"
)
// NewService creates a Gerrit-backed issues.Service using given Gerrit client.
// client must be non-nil.
func NewService(client *gerrit.Client) issues.Service {
s := service{
cl: client,
domain: client.BaseURL().Host,
//users: users,
}
//s.currentUser, s.currentUserErr = s.users.GetAuthenticatedSpec(context.TODO())
return s
}
type service struct {
cl *gerrit.Client
domain string
//users users.Service
//currentUser users.UserSpec
//currentUserErr error
}
func (s service) List(ctx context.Context, rs issues.RepoSpec, opt issues.IssueListOptions) ([]issues.Issue, error) {
project := project(rs)
var query string
switch opt.State {
case issues.StateFilter(issues.OpenState):
query = fmt.Sprintf("project:%s status:open", project)
case issues.StateFilter(issues.ClosedState):
query = fmt.Sprintf("project:%s status:closed", project)
case issues.AllStates:
query = fmt.Sprintf("project:%s", project)
}
changes, _, err := s.cl.Changes.QueryChanges(&gerrit.QueryChangeOptions{
QueryOptions: gerrit.QueryOptions{
Query: []string{query},
Limit: 25,
},
ChangeOptions: gerrit.ChangeOptions{
AdditionalFields: []string{"DETAILED_ACCOUNTS", "MESSAGES"},
},
})
if err != nil {
return nil, err
}
var is []issues.Issue
for _, change := range *changes {
if change.Status == "DRAFT" {
continue
}
is = append(is, issues.Issue{
ID: uint64(change.Number),
State: state(change.Status),
Title: change.Subject,
//Labels: labels, // TODO.
Comment: issues.Comment{
User: s.gerritUser(change.Owner),
CreatedAt: time.Time(change.Created),
},
Replies: len(change.Messages),
})
}
//sort.Sort(sort.Reverse(byID(is))) // For some reason, IDs don't completely line up with created times.
sort.Slice(is, func(i, j int) bool {
return is[i].CreatedAt.After(is[j].CreatedAt)
})
return is, nil
}
func (s service) Count(_ context.Context, rs issues.RepoSpec, opt issues.IssueListOptions) (uint64, error) {
// TODO.
return 0, nil
}
func (s service) Get(ctx context.Context, _ issues.RepoSpec, id uint64) (issues.Issue, error) {
change, _, err := s.cl.Changes.GetChange(fmt.Sprint(id), &gerrit.ChangeOptions{
AdditionalFields: []string{"DETAILED_ACCOUNTS"},
})
if err != nil {
return issues.Issue{}, err
}
if change.Status == "DRAFT" {
return issues.Issue{}, os.ErrNotExist
}
return issues.Issue{
ID: id,
State: state(change.Status),
Title: change.Subject,
Comment: issues.Comment{
User: s.gerritUser(change.Owner),
CreatedAt: time.Time(change.Created),
Editable: false,
},
}, nil
}
func state(status string) issues.State {
switch status {
case "NEW":
return issues.OpenState
case "ABANDONED", "MERGED":
return issues.ClosedState
case "DRAFT":
panic("not sure how to deal with DRAFT status")
default:
panic("unreachable")
}
}
func (s service) ListComments(ctx context.Context, _ issues.RepoSpec, id uint64, opt *issues.ListOptions) ([]issues.Comment, error) {
// TODO: Pagination. Respect opt.Start and opt.Length, if given.
change, _, err := s.cl.Changes.GetChangeDetail(fmt.Sprint(id), nil)
if err != nil {
return nil, err
}
var comments []issues.Comment
for idx, message := range change.Messages {
comments = append(comments, issues.Comment{
ID: uint64(idx), // TODO: message.ID is not uint64; e.g., "bfba753d015916303152305cee7152ea7a112fe0".
User: s.gerritUser(message.Author),
CreatedAt: time.Time(message.Date),
Body: message.Message,
Editable: false,
})
}
return comments, nil
}
func (s service) ListEvents(ctx context.Context, _ issues.RepoSpec, id uint64, opt *issues.ListOptions) ([]issues.Event, error) {
// TODO.
return nil, nil
}
func (s service) CreateComment(_ context.Context, rs issues.RepoSpec, id uint64, c issues.Comment) (issues.Comment, error) {
// TODO.
return issues.Comment{}, fmt.Errorf("CreateComment: not implemented")
}
func (s service) Create(_ context.Context, rs issues.RepoSpec, i issues.Issue) (issues.Issue, error) {
// TODO.
return issues.Issue{}, fmt.Errorf("Create: not implemented")
}
func (s service) Edit(_ context.Context, rs issues.RepoSpec, id uint64, ir issues.IssueRequest) (issues.Issue, []issues.Event, error) {
// TODO.
return issues.Issue{}, nil, fmt.Errorf("Edit: not implemented")
}
func (s service) EditComment(ctx context.Context, rs issues.RepoSpec, id uint64, cr issues.CommentRequest) (issues.Comment, error) {
// TODO.
return issues.Comment{}, fmt.Errorf("EditComment: not implemented")
}
func (s service) gerritUser(user gerrit.AccountInfo) users.User {
return users.User{
UserSpec: users.UserSpec{
ID: uint64(user.AccountID),
Domain: s.domain,
},
Login: user.Name, //user.Username, // TODO.
Name: user.Name,
//Email: user.Email,
AvatarURL: fmt.Sprintf("https://%s/accounts/%d/avatar?s=96", s.domain, user.AccountID),
}
}
func project(rs issues.RepoSpec) string {
if i := strings.IndexByte(rs.URI, '/'); i != -1 {
return rs.URI[i+1:]
}
return ""
}
// byID implements sort.Interface.
type byID []issues.Issue
func (s byID) Len() int { return len(s) }
func (s byID) Less(i, j int) bool { return s[i].ID < s[j].ID }
func (s byID) Swap(i, j int) { s[i], s[j] = s[j], s[i] }