bridge/gitlab: add issue iterator

Amine Hilaly created

Change summary

bridge/gitlab/iterator.go | 212 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 212 insertions(+)

Detailed changes

bridge/gitlab/iterator.go 🔗

@@ -0,0 +1,212 @@
+package gitlab
+
+import (
+	"time"
+
+	"github.com/xanzy/go-gitlab"
+)
+
+type issueIterator struct {
+	page  int
+	index int
+	cache []*gitlab.Issue
+}
+
+type commentIterator struct {
+	page  int
+	index int
+	cache []*gitlab.Note
+}
+
+type iterator struct {
+	// gitlab api v4 client
+	gc *gitlab.Client
+
+	// if since is given the iterator will query only the updated
+	// issues after this date
+	since time.Time
+
+	// project id
+	project string
+
+	// number of issues and notes to query at once
+	capacity int
+
+	// sticky error
+	err error
+
+	// issues iterator
+	issue *issueIterator
+
+	// comments iterator
+	comment *commentIterator
+}
+
+// NewIterator create a new iterator
+func NewIterator(projectID, token string, capacity int, since time.Time) *iterator {
+	return &iterator{
+		gc:       buildClient(token),
+		project:  projectID,
+		since:    since,
+		capacity: capacity,
+		issue: &issueIterator{
+			index: -1,
+			page:  1,
+		},
+		comment: &commentIterator{
+			index: -1,
+			page:  1,
+		},
+	}
+}
+
+// Error return last encountered error
+func (i *iterator) Error() error {
+	return i.err
+}
+
+func (i *iterator) getIssues() ([]*gitlab.Issue, error) {
+	scope := "all"
+	issues, _, err := i.gc.Issues.ListProjectIssues(
+		i.project,
+		&gitlab.ListProjectIssuesOptions{
+			ListOptions: gitlab.ListOptions{
+				Page:    i.issue.page,
+				PerPage: i.capacity,
+			},
+			Scope:        &scope,
+			UpdatedAfter: &i.since,
+		},
+	)
+
+	return issues, err
+}
+
+func (i *iterator) NextIssue() bool {
+	// first query
+	if i.issue.cache == nil {
+		issues, err := i.getIssues()
+		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++
+		return true
+	}
+
+	if i.err != nil {
+		return false
+	}
+
+	// move cursor index
+	if i.issue.index < min(i.capacity, len(i.issue.cache)) {
+		i.issue.index++
+		return true
+	}
+
+	// query next issues
+	issues, err := i.getIssues()
+	if err != nil {
+		i.err = err
+		return false
+	}
+
+	// no more issues to query
+	if len(issues) == 0 {
+		return false
+	}
+
+	i.issue.page++
+	i.issue.index = 0
+	i.comment.index = 0
+
+	return true
+}
+
+func (i *iterator) IssueValue() *gitlab.Issue {
+	return i.issue.cache[i.issue.index]
+}
+
+func (i *iterator) getComments() ([]*gitlab.Note, error) {
+	notes, _, err := i.gc.Notes.ListIssueNotes(
+		i.project,
+		i.IssueValue().IID,
+		&gitlab.ListIssueNotesOptions{
+			ListOptions: gitlab.ListOptions{
+				Page:    i.issue.page,
+				PerPage: i.capacity,
+			},
+		},
+	)
+
+	return notes, err
+}
+
+func (i *iterator) NextComment() bool {
+	if i.err != nil {
+		return false
+	}
+
+	if len(i.comment.cache) == 0 {
+		// query next issues
+		comments, err := i.getComments()
+		if err != nil {
+			i.err = err
+			return false
+		}
+
+		if len(comments) == 0 {
+			i.comment.index = 0
+			i.comment.page = 1
+			return false
+		}
+
+		i.comment.page++
+		i.comment.index = 0
+
+		return true
+	}
+
+	// move cursor index
+	if i.comment.index < min(i.capacity, len(i.comment.cache)) {
+		i.comment.index++
+		return true
+	}
+
+	// query next issues
+	comments, err := i.getComments()
+	if err != nil {
+		i.err = err
+		return false
+	}
+
+	if len(comments) == 0 {
+		i.comment.index = 0
+		i.comment.page = 1
+		return false
+	}
+
+	i.comment.page++
+	i.comment.index = 0
+
+	return false
+}
+
+func (i *iterator) CommentValue() *gitlab.Note {
+	return i.comment.cache[i.comment.index]
+}
+
+func min(a, b int) int {
+	if a > b {
+		return b
+	}
+
+	return a
+}