1package github
2
3import (
4 "context"
5 "time"
6
7 "github.com/MichaelMure/git-bug/bridge/core"
8 "github.com/shurcooL/githubv4"
9)
10
11/**
12type iterator interface {
13 Count() int
14 Error() error
15
16 NextIssue() bool
17 NextIssueEdit() bool
18 NextTimeline() bool
19 NextCommentEdit() bool
20
21 IssueValue() issueTimeline
22 IssueEditValue() userContentEdit
23 TimelineValue() timelineItem
24 CommentEditValue() userContentEdit
25}
26*/
27
28type indexer struct{ index int }
29
30type issueEditIterator struct {
31 index int
32 query issueEditQuery
33 variables map[string]interface{}
34}
35
36type commentEditIterator struct {
37 index int
38 query commentEditQuery
39 variables map[string]interface{}
40}
41
42type timelineIterator struct {
43 index int
44 query issueTimelineQuery
45 variables map[string]interface{}
46
47 issueEdit indexer
48 commentEdit indexer
49}
50
51type iterator struct {
52 // github graphql client
53 gc *githubv4.Client
54
55 // if since is given the iterator will query only the updated
56 // and created issues after this date
57 since time.Time
58
59 // number of timelines/userEditcontent/issueEdit to query
60 // at a time more capacity = more used memory = less queries
61 // to make
62 capacity int
63
64 // sticky error
65 err error
66
67 // count to keep track of the number of imported issues
68 count int
69
70 // timeline iterator
71 timeline timelineIterator
72
73 // issue edit iterator
74 issueEdit issueEditIterator
75
76 // comment edit iterator
77 commentEdit commentEditIterator
78}
79
80func newIterator(conf core.Configuration, since time.Time) *iterator {
81 return &iterator{
82 since: since,
83 gc: buildClient(conf),
84 capacity: 8,
85 count: -1,
86
87 timeline: timelineIterator{
88 index: -1,
89 issueEdit: indexer{-1},
90 commentEdit: indexer{-1},
91 variables: map[string]interface{}{
92 "owner": githubv4.String(conf["user"]),
93 "name": githubv4.String(conf["project"]),
94 },
95 },
96 commentEdit: commentEditIterator{
97 index: -1,
98 variables: map[string]interface{}{
99 "owner": githubv4.String(conf["user"]),
100 "name": githubv4.String(conf["project"]),
101 },
102 },
103 issueEdit: issueEditIterator{
104 index: -1,
105 variables: map[string]interface{}{
106 "owner": githubv4.String(conf["user"]),
107 "name": githubv4.String(conf["project"]),
108 },
109 },
110 }
111}
112
113// init issue timeline variables
114func (i *iterator) initTimelineQueryVariables() {
115 i.timeline.variables["issueFirst"] = githubv4.Int(1)
116 i.timeline.variables["issueAfter"] = (*githubv4.String)(nil)
117 i.timeline.variables["issueSince"] = githubv4.DateTime{Time: i.since}
118 i.timeline.variables["timelineFirst"] = githubv4.Int(i.capacity)
119 i.timeline.variables["timelineAfter"] = (*githubv4.String)(nil)
120 i.timeline.variables["issueEditLast"] = githubv4.Int(i.capacity)
121 i.timeline.variables["issueEditBefore"] = (*githubv4.String)(nil)
122 i.timeline.variables["commentEditLast"] = githubv4.Int(i.capacity)
123 i.timeline.variables["commentEditBefore"] = (*githubv4.String)(nil)
124}
125
126// init issue edit variables
127func (i *iterator) initIssueEditQueryVariables() {
128 i.issueEdit.variables["issueFirst"] = githubv4.Int(1)
129 i.issueEdit.variables["issueAfter"] = i.timeline.variables["issueAfter"]
130 i.issueEdit.variables["issueSince"] = githubv4.DateTime{Time: i.since}
131 i.issueEdit.variables["issueEditLast"] = githubv4.Int(i.capacity)
132 i.issueEdit.variables["issueEditBefore"] = (*githubv4.String)(nil)
133}
134
135// init issue comment variables
136func (i *iterator) initCommentEditQueryVariables() {
137 i.commentEdit.variables["issueFirst"] = githubv4.Int(1)
138 i.commentEdit.variables["issueAfter"] = i.timeline.variables["issueAfter"]
139 i.commentEdit.variables["issueSince"] = githubv4.DateTime{Time: i.since}
140 i.commentEdit.variables["timelineFirst"] = githubv4.Int(1)
141 i.commentEdit.variables["timelineAfter"] = (*githubv4.String)(nil)
142 i.commentEdit.variables["commentEditLast"] = githubv4.Int(i.capacity)
143 i.commentEdit.variables["commentEditBefore"] = (*githubv4.String)(nil)
144}
145
146// reverse UserContentEdits arrays in both of the issue and
147// comment timelines
148func (i *iterator) reverseTimelineEditNodes() {
149 reverseEdits(i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes)
150 for index, ce := range i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges {
151 if ce.Node.Typename == "IssueComment" && len(i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges) != 0 {
152 reverseEdits(i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[index].Node.IssueComment.UserContentEdits.Nodes)
153 }
154 }
155}
156
157// Error .
158func (i *iterator) Error() error {
159 return i.err
160}
161
162// Count .
163func (i *iterator) Count() int {
164 return i.count
165}
166
167func (i *iterator) NextIssue() bool {
168 // we make the first move
169 if i.count == -1 {
170
171 // init variables and goto queryIssue block
172 i.initTimelineQueryVariables()
173 goto queryIssue
174 }
175
176 if i.err != nil {
177 return false
178 }
179
180 if !i.timeline.query.Repository.Issues.PageInfo.HasNextPage {
181 return false
182 }
183
184 // if we have more pages updates variables and query them
185 i.timeline.variables["timelineAfter"] = (*githubv4.String)(nil)
186 i.timeline.variables["issueAfter"] = i.timeline.query.Repository.Issues.PageInfo.EndCursor
187 i.timeline.index = -1
188
189 // query issue block
190queryIssue:
191 if err := i.gc.Query(context.TODO(), &i.timeline.query, i.timeline.variables); err != nil {
192 i.err = err
193 return false
194 }
195
196 if len(i.timeline.query.Repository.Issues.Nodes) == 0 {
197 return false
198 }
199
200 i.reverseTimelineEditNodes()
201 i.count++
202 return true
203}
204
205func (i *iterator) IssueValue() issueTimeline {
206 return i.timeline.query.Repository.Issues.Nodes[0]
207}
208
209func (i *iterator) NextTimeline() bool {
210 if i.err != nil {
211 return false
212 }
213
214 if len(i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges) == 0 {
215 return false
216 }
217
218 if i.timeline.index < min(i.capacity, len(i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges))-1 {
219 i.timeline.index++
220 return true
221 }
222
223 if !i.timeline.query.Repository.Issues.Nodes[0].Timeline.PageInfo.HasNextPage {
224 return false
225 }
226
227 // more timelines, query them
228 i.timeline.variables["timelineAfter"] = i.timeline.query.Repository.Issues.Nodes[0].Timeline.PageInfo.EndCursor
229 if err := i.gc.Query(context.TODO(), &i.timeline.query, i.timeline.variables); err != nil {
230 i.err = err
231 return false
232 }
233
234 i.reverseTimelineEditNodes()
235 i.timeline.index = 0
236 return true
237}
238
239func (i *iterator) TimelineValue() timelineItem {
240 return i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node
241}
242
243func (i *iterator) timelineCursor() string {
244 return ""
245}
246
247func (i *iterator) NextIssueEdit() bool {
248 if i.err != nil {
249 return false
250 }
251
252 // this mean we looped over all available issue edits in the timeline.
253 // now we have to use i.issueEditQuery
254 if i.timeline.issueEdit.index == -2 {
255 if i.issueEdit.index < min(i.capacity, len(i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes))-1 {
256 i.issueEdit.index++
257 return true
258 }
259
260 if !i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.PageInfo.HasPreviousPage {
261 i.timeline.issueEdit.index = -1
262 i.issueEdit.index = -1
263 return false
264 }
265
266 // if there is more edits, query them
267 i.issueEdit.variables["issueEditBefore"] = i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.PageInfo.StartCursor
268 goto queryIssueEdit
269 }
270
271 // if there is no edits
272 if len(i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes) == 0 {
273 return false
274 }
275
276 // loop over them timeline comment edits
277 if i.timeline.issueEdit.index < min(i.capacity, len(i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes))-1 {
278 i.timeline.issueEdit.index++
279 return true
280 }
281
282 if !i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.PageInfo.HasPreviousPage {
283 i.timeline.issueEdit.index = -1
284 return false
285 }
286
287 // if there is more edits, query them
288 i.initIssueEditQueryVariables()
289 i.issueEdit.variables["issueEditBefore"] = i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.PageInfo.StartCursor
290
291queryIssueEdit:
292 if err := i.gc.Query(context.TODO(), &i.issueEdit.query, i.issueEdit.variables); err != nil {
293 i.err = err
294 //i.timeline.issueEdit.index = -1
295 return false
296 }
297
298 // reverse issue edits because github
299 reverseEdits(i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes)
300
301 // this is not supposed to happen
302 if len(i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes) == 0 {
303 i.timeline.issueEdit.index = -1
304 return false
305 }
306
307 i.issueEdit.index = 0
308 i.timeline.issueEdit.index = -2
309 return true
310}
311
312func (i *iterator) IssueEditValue() userContentEdit {
313 // if we are using issue edit query
314 if i.timeline.issueEdit.index == -2 {
315 return i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes[i.issueEdit.index]
316 }
317
318 // else get it from timeline issue edit query
319 return i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes[i.timeline.issueEdit.index]
320}
321
322func (i *iterator) NextCommentEdit() bool {
323 if i.err != nil {
324 return false
325 }
326
327 // same as NextIssueEdit
328 if i.timeline.commentEdit.index == -2 {
329
330 if i.commentEdit.index < min(i.capacity, len(i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.Nodes))-1 {
331 i.commentEdit.index++
332 return true
333 }
334
335 if !i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.PageInfo.HasPreviousPage {
336 i.timeline.commentEdit.index = -1
337 i.commentEdit.index = -1
338 return false
339 }
340
341 // if there is more comment edits, query them
342 i.commentEdit.variables["commentEditBefore"] = i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.PageInfo.StartCursor
343 goto queryCommentEdit
344 }
345
346 // if there is no comment edits
347 if len(i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.Nodes) == 0 {
348 return false
349 }
350
351 // loop over them timeline comment edits
352 if i.timeline.commentEdit.index < min(i.capacity, len(i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.Nodes))-1 {
353 i.timeline.commentEdit.index++
354 return true
355 }
356
357 if !i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.PageInfo.HasPreviousPage {
358 i.timeline.commentEdit.index = -1
359 return false
360 }
361
362 // if there is more comment edits, query them
363
364 i.initCommentEditQueryVariables()
365 if i.timeline.index == 0 {
366 i.commentEdit.variables["timelineAfter"] = i.timeline.query.Repository.Issues.Nodes[0].Timeline.PageInfo.EndCursor
367 } else {
368 i.commentEdit.variables["timelineAfter"] = i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index-1].Cursor
369 }
370
371 i.commentEdit.variables["commentEditBefore"] = i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.PageInfo.StartCursor
372
373queryCommentEdit:
374 if err := i.gc.Query(context.TODO(), &i.commentEdit.query, i.commentEdit.variables); err != nil {
375 i.err = err
376 return false
377 }
378
379 // this is not supposed to happen
380 if len(i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.Nodes) == 0 {
381 i.timeline.commentEdit.index = -1
382 return false
383 }
384
385 reverseEdits(i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.Nodes)
386
387 i.commentEdit.index = 0
388 i.timeline.commentEdit.index = -2
389 return true
390}
391
392func (i *iterator) CommentEditValue() userContentEdit {
393 if i.timeline.commentEdit.index == -2 {
394 return i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.Nodes[i.commentEdit.index]
395 }
396
397 return i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.Nodes[i.timeline.commentEdit.index]
398}
399
400func min(a, b int) int {
401 if a > b {
402 return b
403 }
404
405 return a
406}