package gitlab
import (
"context"
"sort"
"time"
"github.com/xanzy/go-gitlab"
)
type issueIterator struct {
page int
index int
cache []*gitlab.Issue
}
type noteIterator struct {
page int
index int
cache []*gitlab.Note
}
// Since Gitlab does not return the label events items in the correct order
// we need to sort the list our selfs and stop relying on the pagination model
// #BecauseGitlab
type labelEventIterator struct {
index int
cache []*gitlab.LabelEvent
}
func (l *labelEventIterator) Len() int {
return len(l.cache)
}
func (l *labelEventIterator) Swap(i, j int) {
l.cache[i], l.cache[j] = l.cache[j], l.cache[i]
}
func (l *labelEventIterator) Less(i, j int) bool {
return l.cache[i].ID < l.cache[j].ID
}
type iterator struct {
// gitlab api v4 client
gc *gitlab.Client
// if since is given the iterator will query only the issues
// updated after this date
since time.Time
// project id
project string
// number of issues and notes to query at once
capacity int
// shared context
ctx context.Context
// sticky error
err error
// issues iterator
issue *issueIterator
// notes iterator
note *noteIterator
// labelEvent iterator
labelEvent *labelEventIterator
}
// NewIterator create a new iterator
func NewIterator(ctx context.Context, capacity int, projectID, token string, since time.Time) *iterator {
return &iterator{
gc: buildClient(token),
project: projectID,
since: since,
capacity: capacity,
ctx: ctx,
issue: &issueIterator{
index: -1,
page: 1,
},
note: ¬eIterator{
index: -1,
page: 1,
},
labelEvent: &labelEventIterator{
index: -1,
},
}
}
// Error return last encountered error
func (i *iterator) Error() error {
return i.err
}
func (i *iterator) getNextIssues() bool {
ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
defer cancel()
issues, _, err := i.gc.Issues.ListProjectIssues(
i.project,
&gitlab.ListProjectIssuesOptions{
ListOptions: gitlab.ListOptions{
Page: i.issue.page,
PerPage: i.capacity,
},
Scope: gitlab.String("all"),
UpdatedAfter: &i.since,
Sort: gitlab.String("asc"),
},
gitlab.WithContext(ctx),
)
if err != nil {
i.err = err
return false
}
// if repository doesn't have any issues
if len(issues) == 0 {
return false
}
i.issue.cache = issues
i.issue.index = 0
i.issue.page++
i.note.index = -1
i.note.cache = nil
return true
}
func (i *iterator) NextIssue() bool {
if i.err != nil {
return false
}
if i.ctx.Err() != nil {
return false
}
// first query
if i.issue.cache == nil {
return i.getNextIssues()
}
// move cursor index
if i.issue.index < len(i.issue.cache)-1 {
i.issue.index++
return true
}
return i.getNextIssues()
}
func (i *iterator) IssueValue() *gitlab.Issue {
return i.issue.cache[i.issue.index]
}
func (i *iterator) getNextNotes() bool {
ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
defer cancel()
notes, _, err := i.gc.Notes.ListIssueNotes(
i.project,
i.IssueValue().IID,
&gitlab.ListIssueNotesOptions{
ListOptions: gitlab.ListOptions{
Page: i.note.page,
PerPage: i.capacity,
},
Sort: gitlab.String("asc"),
OrderBy: gitlab.String("created_at"),
},
gitlab.WithContext(ctx),
)
if err != nil {
i.err = err
return false
}
if len(notes) == 0 {
i.note.index = -1
i.note.page = 1
i.note.cache = nil
return false
}
i.note.cache = notes
i.note.page++
i.note.index = 0
return true
}
func (i *iterator) NextNote() bool {
if i.err != nil {
return false
}
if i.ctx.Err() != nil {
return false
}
if len(i.note.cache) == 0 {
return i.getNextNotes()
}
// move cursor index
if i.note.index < len(i.note.cache)-1 {
i.note.index++
return true
}
return i.getNextNotes()
}
func (i *iterator) NoteValue() *gitlab.Note {
return i.note.cache[i.note.index]
}
func (i *iterator) getLabelEvents() bool {
ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
defer cancel()
// since order is not garanteed we should query all label events
// and sort them by ID
page := 1
hasNextPage := true
for hasNextPage {
labelEvents, _, err := i.gc.ResourceLabelEvents.ListIssueLabelEvents(
i.project,
i.IssueValue().IID,
&gitlab.ListLabelEventsOptions{
ListOptions: gitlab.ListOptions{
Page: page,
PerPage: i.capacity,
},
},
gitlab.WithContext(ctx),
)
if err != nil {
i.err = err
return false
}
page++
hasNextPage = len(labelEvents) != 0
i.labelEvent.cache = append(i.labelEvent.cache, labelEvents...)
}
i.labelEvent.index = 0
sort.Sort(i.labelEvent)
// if the label events list is empty return false
return len(i.labelEvent.cache) != 0
}
// because Gitlab
func (i *iterator) NextLabelEvent() bool {
if i.err != nil {
return false
}
if i.ctx.Err() != nil {
return false
}
if len(i.labelEvent.cache) == 0 {
return i.getLabelEvents()
}
// move cursor index
if i.labelEvent.index < len(i.labelEvent.cache)-1 {
i.labelEvent.index++
return true
}
return false
}
func (i *iterator) LabelEventValue() *gitlab.LabelEvent {
return i.labelEvent.cache[i.labelEvent.index]
}