iterator.go

  1package gitlab
  2
  3import (
  4	"context"
  5	"sort"
  6	"time"
  7
  8	"github.com/xanzy/go-gitlab"
  9)
 10
 11type issueIterator struct {
 12	page  int
 13	index int
 14	cache []*gitlab.Issue
 15}
 16
 17type noteIterator struct {
 18	page  int
 19	index int
 20	cache []*gitlab.Note
 21}
 22
 23// Since Gitlab does not return the label events items in the correct order
 24// we need to sort the list our selfs and stop relying on the pagination model
 25// #BecauseGitlab
 26type labelEventIterator struct {
 27	index int
 28	cache []*gitlab.LabelEvent
 29}
 30
 31func (l *labelEventIterator) Len() int {
 32	return len(l.cache)
 33}
 34
 35func (l *labelEventIterator) Swap(i, j int) {
 36	l.cache[i], l.cache[j] = l.cache[j], l.cache[i]
 37}
 38
 39func (l *labelEventIterator) Less(i, j int) bool {
 40	return l.cache[i].ID < l.cache[j].ID
 41}
 42
 43type iterator struct {
 44	// gitlab api v4 client
 45	gc *gitlab.Client
 46
 47	// if since is given the iterator will query only the issues
 48	// updated after this date
 49	since time.Time
 50
 51	// project id
 52	project string
 53
 54	// number of issues and notes to query at once
 55	capacity int
 56
 57	// shared context
 58	ctx context.Context
 59
 60	// sticky error
 61	err error
 62
 63	// issues iterator
 64	issue *issueIterator
 65
 66	// notes iterator
 67	note *noteIterator
 68
 69	// labelEvent iterator
 70	labelEvent *labelEventIterator
 71}
 72
 73// NewIterator create a new iterator
 74func NewIterator(ctx context.Context, capacity int, projectID, token string, since time.Time) *iterator {
 75	return &iterator{
 76		gc:       buildClient(token),
 77		project:  projectID,
 78		since:    since,
 79		capacity: capacity,
 80		ctx:      ctx,
 81		issue: &issueIterator{
 82			index: -1,
 83			page:  1,
 84		},
 85		note: &noteIterator{
 86			index: -1,
 87			page:  1,
 88		},
 89		labelEvent: &labelEventIterator{
 90			index: -1,
 91		},
 92	}
 93}
 94
 95// Error return last encountered error
 96func (i *iterator) Error() error {
 97	return i.err
 98}
 99
100func (i *iterator) getNextIssues() bool {
101	ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
102	defer cancel()
103
104	issues, _, err := i.gc.Issues.ListProjectIssues(
105		i.project,
106		&gitlab.ListProjectIssuesOptions{
107			ListOptions: gitlab.ListOptions{
108				Page:    i.issue.page,
109				PerPage: i.capacity,
110			},
111			Scope:        gitlab.String("all"),
112			UpdatedAfter: &i.since,
113			Sort:         gitlab.String("asc"),
114		},
115		gitlab.WithContext(ctx),
116	)
117
118	if err != nil {
119		i.err = err
120		return false
121	}
122
123	// if repository doesn't have any issues
124	if len(issues) == 0 {
125		return false
126	}
127
128	i.issue.cache = issues
129	i.issue.index = 0
130	i.issue.page++
131	i.note.index = -1
132	i.note.cache = nil
133
134	return true
135}
136
137func (i *iterator) NextIssue() bool {
138	if i.err != nil {
139		return false
140	}
141
142	if i.ctx.Err() != nil {
143		return false
144	}
145
146	// first query
147	if i.issue.cache == nil {
148		return i.getNextIssues()
149	}
150
151	// move cursor index
152	if i.issue.index < len(i.issue.cache)-1 {
153		i.issue.index++
154		return true
155	}
156
157	return i.getNextIssues()
158}
159
160func (i *iterator) IssueValue() *gitlab.Issue {
161	return i.issue.cache[i.issue.index]
162}
163
164func (i *iterator) getNextNotes() bool {
165	ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
166	defer cancel()
167
168	notes, _, err := i.gc.Notes.ListIssueNotes(
169		i.project,
170		i.IssueValue().IID,
171		&gitlab.ListIssueNotesOptions{
172			ListOptions: gitlab.ListOptions{
173				Page:    i.note.page,
174				PerPage: i.capacity,
175			},
176			Sort:    gitlab.String("asc"),
177			OrderBy: gitlab.String("created_at"),
178		},
179		gitlab.WithContext(ctx),
180	)
181
182	if err != nil {
183		i.err = err
184		return false
185	}
186
187	if len(notes) == 0 {
188		i.note.index = -1
189		i.note.page = 1
190		i.note.cache = nil
191		return false
192	}
193
194	i.note.cache = notes
195	i.note.page++
196	i.note.index = 0
197	return true
198}
199
200func (i *iterator) NextNote() bool {
201	if i.err != nil {
202		return false
203	}
204
205	if i.ctx.Err() != nil {
206		return false
207	}
208
209	if len(i.note.cache) == 0 {
210		return i.getNextNotes()
211	}
212
213	// move cursor index
214	if i.note.index < len(i.note.cache)-1 {
215		i.note.index++
216		return true
217	}
218
219	return i.getNextNotes()
220}
221
222func (i *iterator) NoteValue() *gitlab.Note {
223	return i.note.cache[i.note.index]
224}
225
226func (i *iterator) getLabelEvents() bool {
227	ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
228	defer cancel()
229
230	// since order is not garanteed we should query all label events
231	// and sort them by ID
232	page := 1
233	hasNextPage := true
234	for hasNextPage {
235		labelEvents, _, err := i.gc.ResourceLabelEvents.ListIssueLabelEvents(
236			i.project,
237			i.IssueValue().IID,
238			&gitlab.ListLabelEventsOptions{
239				ListOptions: gitlab.ListOptions{
240					Page:    page,
241					PerPage: i.capacity,
242				},
243			},
244			gitlab.WithContext(ctx),
245		)
246		if err != nil {
247			i.err = err
248			return false
249		}
250
251		page++
252		hasNextPage = len(labelEvents) != 0
253		i.labelEvent.cache = append(i.labelEvent.cache, labelEvents...)
254	}
255
256	i.labelEvent.index = 0
257	sort.Sort(i.labelEvent)
258
259	// if the label events list is empty return false
260	return len(i.labelEvent.cache) != 0
261}
262
263// because Gitlab
264func (i *iterator) NextLabelEvent() bool {
265	if i.err != nil {
266		return false
267	}
268
269	if i.ctx.Err() != nil {
270		return false
271	}
272
273	if len(i.labelEvent.cache) == 0 {
274		return i.getLabelEvents()
275	}
276
277	// move cursor index
278	if i.labelEvent.index < len(i.labelEvent.cache)-1 {
279		i.labelEvent.index++
280		return true
281	}
282
283	return false
284}
285
286func (i *iterator) LabelEventValue() *gitlab.LabelEvent {
287	return i.labelEvent.cache[i.labelEvent.index]
288}