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
23type labelEventIterator struct {
24 page int
25 index int
26 cache []*gitlab.LabelEvent
27}
28
29func (l *labelEventIterator) Len() int {
30 return len(l.cache)
31}
32
33func (l *labelEventIterator) Swap(i, j int) {
34 element := l.cache[i]
35 l.cache[i] = l.cache[j]
36 l.cache[j] = element
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: ¬eIterator{
86 index: -1,
87 page: 1,
88 },
89 labelEvent: &labelEventIterator{
90 index: -1,
91 page: 1,
92 },
93 }
94}
95
96// Error return last encountered error
97func (i *iterator) Error() error {
98 return i.err
99}
100
101func (i *iterator) getNextIssues() bool {
102 ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
103 defer cancel()
104
105 issues, _, err := i.gc.Issues.ListProjectIssues(
106 i.project,
107 &gitlab.ListProjectIssuesOptions{
108 ListOptions: gitlab.ListOptions{
109 Page: i.issue.page,
110 PerPage: i.capacity,
111 },
112 Scope: gitlab.String("all"),
113 UpdatedAfter: &i.since,
114 Sort: gitlab.String("asc"),
115 },
116 gitlab.WithContext(ctx),
117 )
118
119 if err != nil {
120 i.err = err
121 return false
122 }
123
124 // if repository doesn't have any issues
125 if len(issues) == 0 {
126 return false
127 }
128
129 i.issue.cache = issues
130 i.issue.index = 0
131 i.issue.page++
132 i.note.index = -1
133 i.note.cache = nil
134
135 return true
136}
137
138func (i *iterator) NextIssue() bool {
139 if i.err != nil {
140 return false
141 }
142
143 if i.ctx.Err() != nil {
144 return false
145 }
146
147 // first query
148 if i.issue.cache == nil {
149 return i.getNextIssues()
150 }
151
152 // move cursor index
153 if i.issue.index < len(i.issue.cache)-1 {
154 i.issue.index++
155 return true
156 }
157
158 return i.getNextIssues()
159}
160
161func (i *iterator) IssueValue() *gitlab.Issue {
162 return i.issue.cache[i.issue.index]
163}
164
165func (i *iterator) getNextNotes() bool {
166 ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
167 defer cancel()
168
169 notes, _, err := i.gc.Notes.ListIssueNotes(
170 i.project,
171 i.IssueValue().IID,
172 &gitlab.ListIssueNotesOptions{
173 ListOptions: gitlab.ListOptions{
174 Page: i.note.page,
175 PerPage: i.capacity,
176 },
177 Sort: gitlab.String("asc"),
178 OrderBy: gitlab.String("created_at"),
179 },
180 gitlab.WithContext(ctx),
181 )
182
183 if err != nil {
184 i.err = err
185 return false
186 }
187
188 if len(notes) == 0 {
189 i.note.index = -1
190 i.note.page = 1
191 i.note.cache = nil
192 return false
193 }
194
195 i.note.cache = notes
196 i.note.page++
197 i.note.index = 0
198 return true
199}
200
201func (i *iterator) NextNote() bool {
202 if i.err != nil {
203 return false
204 }
205
206 if i.ctx.Err() != nil {
207 return false
208 }
209
210 if len(i.note.cache) == 0 {
211 return i.getNextNotes()
212 }
213
214 // move cursor index
215 if i.note.index < len(i.note.cache)-1 {
216 i.note.index++
217 return true
218 }
219
220 return i.getNextNotes()
221}
222
223func (i *iterator) NoteValue() *gitlab.Note {
224 return i.note.cache[i.note.index]
225}
226
227func (i *iterator) getLabelEvents() bool {
228 ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
229 defer cancel()
230
231 hasNextPage := true
232 for hasNextPage {
233 labelEvents, _, err := i.gc.ResourceLabelEvents.ListIssueLabelEvents(
234 i.project,
235 i.IssueValue().IID,
236 &gitlab.ListLabelEventsOptions{
237 ListOptions: gitlab.ListOptions{
238 Page: i.labelEvent.page,
239 PerPage: i.capacity,
240 },
241 },
242 gitlab.WithContext(ctx),
243 )
244 if err != nil {
245 i.err = err
246 return false
247 }
248
249 i.labelEvent.page++
250 hasNextPage = len(labelEvents) != 0
251 i.labelEvent.cache = append(i.labelEvent.cache, labelEvents...)
252 }
253
254 i.labelEvent.page = 1
255 i.labelEvent.index = 0
256 sort.Sort(i.labelEvent)
257
258 // if the label events list is empty return false
259 return len(i.labelEvent.cache) != 0
260}
261
262// because Gitlab
263func (i *iterator) NextLabelEvent() bool {
264 if i.err != nil {
265 return false
266 }
267
268 if i.ctx.Err() != nil {
269 return false
270 }
271
272 if len(i.labelEvent.cache) == 0 {
273 return i.getLabelEvents()
274 }
275
276 // move cursor index
277 if i.labelEvent.index < len(i.labelEvent.cache)-1 {
278 i.labelEvent.index++
279 return true
280 }
281
282 return false
283}
284
285func (i *iterator) LabelEventValue() *gitlab.LabelEvent {
286 return i.labelEvent.cache[i.labelEvent.index]
287}