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, client *gitlab.Client, capacity int, projectID string, since time.Time) *iterator {
75 return &iterator{
76 gc: client,
77 project: projectID,
78 since: since,
79 capacity: capacity,
80 ctx: ctx,
81 issue: &issueIterator{
82 index: -1,
83 page: 1,
84 },
85 note: ¬eIterator{
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}