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, client *gitlab.Client, capacity int, projectID string, since time.Time) *iterator { return &iterator{ gc: client, 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] }