1package github
2
3import (
4 "context"
5 "fmt"
6 "time"
7
8 "github.com/shurcooL/githubv4"
9)
10
11type iterator_A struct {
12 gc *githubv4.Client
13 since time.Time
14 ctx context.Context
15 err error
16 issueIter issueIter
17}
18
19type issueIter struct {
20 iterVars
21 query issueQuery
22 issueEditIter []issueEditIter
23 timelineIter []timelineIter
24}
25
26type issueEditIter struct {
27 iterVars
28 query issueEditQuery_A
29}
30
31type timelineIter struct {
32 iterVars
33 query timelineQuery
34 commentEditIter []commentEditIter
35}
36
37
38type commentEditIter struct {
39 iterVars
40 //query commentEditQuery
41}
42
43type iterVars struct {
44 index int
45 capacity int
46 variables varmap
47}
48
49type varmap map[string]interface{}
50
51
52func NewIterator_A(ctx context.Context, client *githubv4.Client, capacity int, owner, project string, since time.Time) *iterator_A {
53 i := &iterator_A{
54 gc: client,
55 since: since,
56 ctx: ctx,
57 issueIter: issueIter{
58 iterVars: newIterVars(capacity),
59 },
60 }
61 i.issueIter.variables.setOwnerProject(owner, project)
62 for idx := range i.issueIter.issueEditIter {
63 ie := &i.issueIter.issueEditIter[idx]
64 ie.iterVars = newIterVars(capacity)
65 }
66 for i1 := range i.issueIter.timelineIter {
67 tli := &i.issueIter.timelineIter[i1]
68 tli.iterVars = newIterVars(capacity)
69 }
70 i.resetIssueVars()
71 return i
72}
73
74func newIterVars(capacity int) iterVars {
75 return iterVars{
76 index: -1,
77 capacity: capacity,
78 variables: varmap{},
79 }
80}
81
82func (v *varmap) setOwnerProject(owner, project string) {
83 (*v)["owner"] = githubv4.String(owner)
84 (*v)["name"] = githubv4.String(project)
85}
86
87func (i *iterator_A) resetIssueVars() {
88 vars := &i.issueIter.variables
89 (*vars)["issueFirst"] = githubv4.Int(i.issueIter.capacity)
90 (*vars)["issueAfter"] = (*githubv4.String)(nil)
91 // I am not sure if the since variable should be used.
92 //(*vars)["issueSince"] = githubv4.DateTime{Time: i.since}
93 i.issueIter.query.Repository.Issues.PageInfo.HasNextPage = true
94 i.issueIter.query.Repository.Issues.PageInfo.EndCursor = ""
95}
96
97func (i *iterator_A) resetIssueEditVars() {
98 for idx := range i.issueIter.issueEditIter {
99 ie := &i.issueIter.issueEditIter[idx]
100 ie.variables["issueEditLast"] = githubv4.Int(ie.capacity)
101 ie.variables["issueEditBefore"] = (*githubv4.String)(nil)
102 ie.query.Node.Issue.UserContentEdits.PageInfo.HasNextPage = true
103 ie.query.Node.Issue.UserContentEdits.PageInfo.EndCursor = ""
104 }
105}
106
107func (i *iterator_A) resetTimelineVars() {
108 for idx := range i.issueIter.timelineIter {
109 ip := &i.issueIter.timelineIter[idx]
110 ip.variables["timelineFirst"] = githubv4.Int(ip.capacity)
111 ip.variables["timelineAfter"] = (*githubv4.String)(nil)
112 ip.query.Node.Issue.TimelineItems.PageInfo.HasNextPage = true
113 ip.query.Node.Issue.TimelineItems.PageInfo.EndCursor = ""
114 }
115}
116
117func (i *iterator_A) currIssueItem() *issue {
118 return &i.issueIter.query.Repository.Issues.Nodes[i.issueIter.index]
119}
120
121func (i *iterator_A) currIssueEditIter() *issueEditIter {
122 return &i.issueIter.issueEditIter[i.issueIter.index]
123}
124
125func (i *iterator_A) currTimelineIter() *timelineIter {
126 return &i.issueIter.timelineIter[i.issueIter.index]
127}
128
129func (i *iterator_A) currIssueGqlNodeId() githubv4.ID {
130 return i.currIssueItem().Id
131}
132
133// Error return last encountered error
134func (i *iterator_A) Error() error {
135 if i.err != nil {
136 return i.err
137 }
138 return i.ctx.Err() // might return nil
139}
140
141func (i *iterator_A) HasError() bool {
142 return i.err != nil || i.ctx.Err() != nil
143}
144
145func (i *iterator_A) NextIssue() bool {
146 if i.HasError() {
147 return false
148 }
149 index := &i.issueIter.index
150 issues := &i.issueIter.query.Repository.Issues
151 issueItems := &issues.Nodes
152 if 0 <= *index && *index < len(*issueItems)-1 {
153 *index += 1
154 return true
155 }
156
157 if !issues.PageInfo.HasNextPage {
158 return false
159 }
160 nextIssue := i.queryIssue()
161 return nextIssue
162}
163
164func (i *iterator_A) IssueValue() issue {
165 return *i.currIssueItem()
166}
167
168func (i *iterator_A) queryIssue() bool {
169 ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
170 defer cancel()
171 if endCursor := i.issueIter.query.Repository.Issues.PageInfo.EndCursor; endCursor != "" {
172 i.issueIter.variables["issueAfter"] = endCursor
173 }
174 if err := i.gc.Query(ctx, &i.issueIter.query, i.issueIter.variables); err != nil {
175 i.err = err
176 return false
177 }
178 i.resetIssueEditVars()
179 i.resetTimelineVars()
180 issueItems := &i.issueIter.query.Repository.Issues.Nodes
181 if len(*issueItems) <= 0 {
182 i.issueIter.index = -1
183 return false
184 }
185 i.issueIter.index = 0
186 return true
187}
188
189func (i *iterator_A) NextIssueEdit() bool {
190 if i.HasError() {
191 return false
192 }
193 ieIter := i.currIssueEditIter()
194 ieIdx := &ieIter.index
195 ieItems := ieIter.query.Node.Issue.UserContentEdits
196 if 0 <= *ieIdx && *ieIdx < len(ieItems.Nodes)-1 {
197 *ieIdx += 1
198 return i.nextValidIssueEdit()
199 }
200 if !ieItems.PageInfo.HasNextPage {
201 return false
202 }
203 querySucc := i.queryIssueEdit()
204 if !querySucc {
205 return false
206 }
207 return i.nextValidIssueEdit()
208}
209
210func (i *iterator_A) nextValidIssueEdit() bool {
211 // issueEdit.Diff == nil happen if the event is older than early 2018, Github doesn't have the data before that.
212 // Best we can do is to ignore the event.
213 if issueEdit := i.IssueEditValue(); issueEdit.Diff == nil || string(*issueEdit.Diff) == "" {
214 return i.NextIssueEdit()
215 }
216 return true
217}
218
219func (i *iterator_A) IssueEditValue() userContentEdit {
220 iei := i.currIssueEditIter()
221 return iei.query.Node.Issue.UserContentEdits.Nodes[iei.index]
222}
223
224func (i *iterator_A) queryIssueEdit() bool {
225 ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
226 defer cancel()
227 iei := i.currIssueEditIter()
228 if endCursor := iei.query.Node.Issue.UserContentEdits.PageInfo.EndCursor; endCursor != "" {
229 iei.variables["issueEditBefore"] = endCursor
230 }
231 iei.variables["gqlNodeId"] = i.currIssueGqlNodeId()
232 if err := i.gc.Query(ctx, &iei.query, iei.variables); err != nil {
233 i.err = err
234 return false
235 }
236 issueEditItems := iei.query.Node.Issue.UserContentEdits.Nodes
237 if len(issueEditItems) <= 0 {
238 iei.index = -1
239 return false
240 }
241 // The UserContentEditConnection in the Github API serves its elements in reverse chronological
242 // order. For our purpose we have to reverse the edits.
243 reverseEdits(issueEditItems)
244 iei.index = 0
245 return true
246}
247
248func (i *iterator_A) NextTimelineItem() bool {
249 if i.HasError() {
250 return false
251 }
252 tlIter := &i.issueIter.timelineIter[i.issueIter.index]
253 tlIdx := &tlIter.index
254 tlItems := tlIter.query.Node.Issue.TimelineItems
255 if 0 <= *tlIdx && *tlIdx < len(tlItems.Nodes)-1 {
256 *tlIdx += 1
257 return true
258 }
259 if !tlItems.PageInfo.HasNextPage {
260 return false
261 }
262 nextTlItem := i.queryTimeline()
263 return nextTlItem
264}
265
266func (i *iterator_A) TimelineItemValue() timelineItem {
267 tli := i.currTimelineIter()
268 return tli.query.Node.Issue.TimelineItems.Nodes[tli.index]
269}
270
271func (i *iterator_A) queryTimeline() bool {
272 ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
273 defer cancel()
274 tli := i.currTimelineIter()
275 if endCursor := tli.query.Node.Issue.TimelineItems.PageInfo.EndCursor; endCursor != "" {
276 tli.variables["timelineAfter"] = endCursor
277 }
278 tli.variables["gqlNodeId"] = i.currIssueGqlNodeId()
279 if err := i.gc.Query(ctx, &tli.query, tli.variables); err != nil {
280 i.err = err
281 return false
282 }
283 //i.resetCommentEditVars()
284 timelineItems := &tli.query.Node.Issue.TimelineItems
285 if len(timelineItems.Nodes) <= 0 {
286 tli.index = -1
287 return false
288 }
289 tli.index = 0
290 return true
291}
292
293
294
295type indexer struct{ index int }
296
297type issueEditIterator struct {
298 index int
299 query issueEditQuery
300 variables map[string]interface{}
301}
302
303type commentEditIterator struct {
304 index int
305 query commentEditQuery
306 variables map[string]interface{}
307}
308
309type timelineIterator struct {
310 index int
311 query issueTimelineQuery
312 variables map[string]interface{}
313
314 issueEdit indexer
315 commentEdit indexer
316
317 // Alex: It would be really help clearity to get rid of this variable.
318 // lastEndCursor cache the timeline end cursor for one iteration
319 lastEndCursor githubv4.String
320}
321
322type iterator struct {
323 // github graphql client
324 gc *githubv4.Client
325
326 // if since is given the iterator will query only the updated
327 // and created issues after this date
328 since time.Time
329
330 // number of timelines/userEditcontent/issueEdit to query
331 // at a time, more capacity = more used memory = less queries
332 // to make
333 capacity int
334
335 // shared context used for all graphql queries
336 ctx context.Context
337
338 // sticky error
339 err error
340
341 // timeline iterator
342 timeline timelineIterator
343
344 // issue edit iterator
345 issueEdit issueEditIterator
346
347 // comment edit iterator
348 commentEdit commentEditIterator
349}
350
351// NewIterator create and initialize a new iterator
352func NewIterator(ctx context.Context, client *githubv4.Client, capacity int, owner, project string, since time.Time) *iterator {
353 i := &iterator{
354 gc: client,
355 since: since,
356 capacity: capacity,
357 ctx: ctx,
358 timeline: timelineIterator{
359 index: -1,
360 issueEdit: indexer{-1},
361 commentEdit: indexer{-1},
362 variables: map[string]interface{}{
363 "owner": githubv4.String(owner),
364 "name": githubv4.String(project),
365 },
366 },
367 commentEdit: commentEditIterator{
368 index: -1,
369 variables: map[string]interface{}{
370 "owner": githubv4.String(owner),
371 "name": githubv4.String(project),
372 },
373 },
374 issueEdit: issueEditIterator{
375 index: -1,
376 variables: map[string]interface{}{
377 "owner": githubv4.String(owner),
378 "name": githubv4.String(project),
379 },
380 },
381 }
382
383 i.initTimelineQueryVariables()
384 return i
385}
386
387// init issue timeline variables
388func (i *iterator) initTimelineQueryVariables() {
389 i.timeline.variables["issueFirst"] = githubv4.Int(1) // each query one single issue only
390 i.timeline.variables["issueAfter"] = (*githubv4.String)(nil)
391 i.timeline.variables["issueSince"] = githubv4.DateTime{Time: i.since}
392 i.timeline.variables["timelineFirst"] = githubv4.Int(i.capacity)
393 i.timeline.variables["timelineAfter"] = (*githubv4.String)(nil)
394 // Fun fact, github provide the comment edition in reverse chronological
395 // order, because haha. Look at me, I'm dying of laughter.
396 i.timeline.variables["issueEditLast"] = githubv4.Int(i.capacity)
397 i.timeline.variables["issueEditBefore"] = (*githubv4.String)(nil)
398 i.timeline.variables["commentEditLast"] = githubv4.Int(i.capacity)
399 i.timeline.variables["commentEditBefore"] = (*githubv4.String)(nil)
400}
401
402// init issue edit variables
403func (i *iterator) initIssueEditQueryVariables() {
404 i.issueEdit.variables["issueFirst"] = githubv4.Int(1)
405 i.issueEdit.variables["issueAfter"] = i.timeline.variables["issueAfter"]
406 i.issueEdit.variables["issueSince"] = githubv4.DateTime{Time: i.since}
407 i.issueEdit.variables["issueEditLast"] = githubv4.Int(i.capacity)
408 i.issueEdit.variables["issueEditBefore"] = (*githubv4.String)(nil)
409}
410
411// init issue comment variables
412func (i *iterator) initCommentEditQueryVariables() {
413 i.commentEdit.variables["issueFirst"] = githubv4.Int(1)
414 i.commentEdit.variables["issueAfter"] = i.timeline.variables["issueAfter"]
415 i.commentEdit.variables["issueSince"] = githubv4.DateTime{Time: i.since}
416 i.commentEdit.variables["timelineFirst"] = githubv4.Int(1)
417 i.commentEdit.variables["timelineAfter"] = (*githubv4.String)(nil)
418 i.commentEdit.variables["commentEditLast"] = githubv4.Int(i.capacity)
419 i.commentEdit.variables["commentEditBefore"] = (*githubv4.String)(nil)
420}
421
422// reverse UserContentEdits arrays in both of the issue and
423// comment timelines
424func (i *iterator) reverseTimelineEditNodes() {
425 node := i.timeline.query.Repository.Issues.Nodes[0]
426 reverseEdits(node.UserContentEdits.Nodes)
427 for index, ce := range node.TimelineItems.Edges {
428 if ce.Node.Typename == "IssueComment" && len(node.TimelineItems.Edges) != 0 {
429 reverseEdits(node.TimelineItems.Edges[index].Node.IssueComment.UserContentEdits.Nodes)
430 }
431 }
432}
433
434// Error return last encountered error
435func (i *iterator) Error() error {
436 return i.err
437}
438
439func (i *iterator) queryIssue() bool {
440 ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
441 defer cancel()
442
443 if err := i.gc.Query(ctx, &i.timeline.query, i.timeline.variables); err != nil {
444 i.err = err
445 return false
446 }
447
448 issues := i.timeline.query.Repository.Issues.Nodes
449 if len(issues) == 0 {
450 return false
451 }
452
453 i.reverseTimelineEditNodes()
454 return true
455}
456
457// NextIssue try to query the next issue and return true. Only one issue is
458// queried at each call.
459func (i *iterator) NextIssue() bool {
460 if i.err != nil {
461 return false
462 }
463
464 if i.ctx.Err() != nil {
465 return false
466 }
467
468 // if $issueAfter variable is nil we can directly make the first query
469 if i.timeline.variables["issueAfter"] == (*githubv4.String)(nil) {
470 nextIssue := i.queryIssue()
471 // prevent from infinite loop by setting a non nil cursor
472 issues := i.timeline.query.Repository.Issues
473 i.timeline.variables["issueAfter"] = issues.PageInfo.EndCursor
474 return nextIssue
475 }
476
477 issues := i.timeline.query.Repository.Issues
478 if !issues.PageInfo.HasNextPage {
479 return false
480 }
481
482 // if we have more issues, query them
483 i.timeline.variables["timelineAfter"] = (*githubv4.String)(nil)
484 i.timeline.index = -1
485
486 timelineEndCursor := issues.Nodes[0].TimelineItems.PageInfo.EndCursor
487 // store cursor for future use
488 i.timeline.lastEndCursor = timelineEndCursor
489
490 // query issue block
491 nextIssue := i.queryIssue()
492 i.timeline.variables["issueAfter"] = issues.PageInfo.EndCursor
493
494 return nextIssue
495}
496
497// IssueValue return the actual issue value
498func (i *iterator) IssueValue() issueTimeline {
499 issues := i.timeline.query.Repository.Issues
500 return issues.Nodes[0]
501}
502
503// NextTimelineItem return true if there is a next timeline item and increments the index by one.
504// It is used iterates over all the timeline items. Extra queries are made if it is necessary.
505func (i *iterator) NextTimelineItem() bool {
506 if i.err != nil {
507 return false
508 }
509
510 if i.ctx.Err() != nil {
511 return false
512 }
513
514 timelineItems := i.timeline.query.Repository.Issues.Nodes[0].TimelineItems
515 // after NextIssue call it's good to check wether we have some timelineItems items or not
516 // Alex: Correct?
517 if len(timelineItems.Edges) == 0 {
518 return false
519 }
520
521 if i.timeline.index < len(timelineItems.Edges)-1 {
522 i.timeline.index++
523 return true
524 }
525
526 if !timelineItems.PageInfo.HasNextPage {
527 return false
528 }
529
530 i.timeline.lastEndCursor = timelineItems.PageInfo.EndCursor
531
532 // more timelines, query them
533 i.timeline.variables["timelineAfter"] = timelineItems.PageInfo.EndCursor
534 // HACK
535 var query timelineItemsQuery
536 // var variables map[string]interface{}
537 variables := make(map[string]interface{})
538 variables["owner"] = i.timeline.variables["owner"]
539 variables["name"] = i.timeline.variables["name"]
540 variables["issueNumber"] = i.timeline.query.Repository.Issues.Nodes[0].Number
541 fmt.Println("### Alex using issue number ", i.timeline.query.Repository.Issues.Nodes[0].Number)
542 variables["timelineFirst"] = i.timeline.variables["timelineFirst"]
543 variables["timelineAfter"] = i.timeline.variables["timelineAfter"]
544 variables["commentEditLast"] = i.timeline.variables["commentEditLast"]
545 variables["commentEditBefore"] = i.timeline.variables["commentEditBefore"]
546
547 ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
548 defer cancel()
549
550 // if err := i.gc.Query(ctx, &i.timeline.query, i.timeline.variables); err != nil {
551 if err := i.gc.Query(ctx, &query, variables); err != nil {
552 i.err = err
553 return false
554 }
555 // HACK
556 fmt.Println("### Alex after the query")
557 i.timeline.variables["timelineFirst"] = variables["timelineFirst"]
558 i.timeline.variables["timelineAfter"] = variables["timelineAfter"]
559 i.timeline.variables["commentEditLast"] = variables["commentEditLast"]
560 i.timeline.variables["commentEditBefore"] = variables["commentEditBefore"]
561 i.timeline.query.Repository.Issues.Nodes[0].TimelineItems = query.Repository.Issue.TimelineItems
562
563 timelineItems = i.timeline.query.Repository.Issues.Nodes[0].TimelineItems
564 // (in case github returns something weird) just for safety: better return a false than a panic
565 if len(timelineItems.Edges) == 0 {
566 return false
567 }
568
569 i.reverseTimelineEditNodes()
570 i.timeline.index = 0
571 return true
572}
573
574// TimelineItemValue return the actual timeline item value
575func (i *iterator) TimelineItemValue() timelineItem {
576 timelineItems := i.timeline.query.Repository.Issues.Nodes[0].TimelineItems
577 return timelineItems.Edges[i.timeline.index].Node
578}
579
580func (i *iterator) queryIssueEdit() bool {
581 ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
582 defer cancel()
583
584 if err := i.gc.Query(ctx, &i.issueEdit.query, i.issueEdit.variables); err != nil {
585 i.err = err
586 //i.timeline.issueEdit.index = -1
587 return false
588 }
589
590 issueEdits := i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits
591 // reverse issue edits because github
592 reverseEdits(issueEdits.Nodes)
593
594 // this is not supposed to happen
595 if len(issueEdits.Nodes) == 0 {
596 i.timeline.issueEdit.index = -1
597 return false
598 }
599
600 i.issueEdit.index = 0
601 i.timeline.issueEdit.index = -2
602 return i.nextValidIssueEdit()
603}
604
605func (i *iterator) nextValidIssueEdit() bool {
606 // issueEdit.Diff == nil happen if the event is older than early 2018, Github doesn't have the data before that.
607 // Best we can do is to ignore the event.
608 if issueEdit := i.IssueEditValue(); issueEdit.Diff == nil || string(*issueEdit.Diff) == "" {
609 return i.NextIssueEdit()
610 }
611 return true
612}
613
614// NextIssueEdit return true if there is a next issue edit and increments the index by one.
615// It is used iterates over all the issue edits. Extra queries are made if it is necessary.
616func (i *iterator) NextIssueEdit() bool {
617 if i.err != nil {
618 return false
619 }
620
621 if i.ctx.Err() != nil {
622 return false
623 }
624
625 // this mean we looped over all available issue edits in the timeline.
626 // now we have to use i.issueEditQuery
627 if i.timeline.issueEdit.index == -2 {
628 issueEdits := i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits
629 if i.issueEdit.index < len(issueEdits.Nodes)-1 {
630 i.issueEdit.index++
631 return i.nextValidIssueEdit()
632 }
633
634 if !issueEdits.PageInfo.HasPreviousPage {
635 i.timeline.issueEdit.index = -1
636 i.issueEdit.index = -1
637 return false
638 }
639
640 // if there is more edits, query them
641 i.issueEdit.variables["issueEditBefore"] = issueEdits.PageInfo.StartCursor
642 return i.queryIssueEdit()
643 }
644
645 issueEdits := i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits
646 // if there is no edit, the UserContentEdits given by github is empty. That
647 // means that the original message is given by the issue message.
648 //
649 // if there is edits, the UserContentEdits given by github contains both the
650 // original message and the following edits. The issue message give the last
651 // version so we don't care about that.
652 //
653 // the tricky part: for an issue older than the UserContentEdits API, github
654 // doesn't have the previous message version anymore and give an edition
655 // with .Diff == nil. We have to filter them.
656 if len(issueEdits.Nodes) == 0 {
657 return false
658 }
659
660 // loop over them timeline comment edits
661 if i.timeline.issueEdit.index < len(issueEdits.Nodes)-1 {
662 i.timeline.issueEdit.index++
663 return i.nextValidIssueEdit()
664 }
665
666 if !issueEdits.PageInfo.HasPreviousPage {
667 i.timeline.issueEdit.index = -1
668 return false
669 }
670
671 // if there is more edits, query them
672 i.initIssueEditQueryVariables()
673 i.issueEdit.variables["issueEditBefore"] = issueEdits.PageInfo.StartCursor
674 return i.queryIssueEdit()
675}
676
677// IssueEditValue return the actual issue edit value
678func (i *iterator) IssueEditValue() userContentEdit {
679 // if we are using issue edit query
680 if i.timeline.issueEdit.index == -2 {
681 issueEdits := i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits
682 return issueEdits.Nodes[i.issueEdit.index]
683 }
684
685 issueEdits := i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits
686 // else get it from timeline issue edit query
687 return issueEdits.Nodes[i.timeline.issueEdit.index]
688}
689
690func (i *iterator) queryCommentEdit() bool {
691 ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
692 defer cancel()
693
694 if err := i.gc.Query(ctx, &i.commentEdit.query, i.commentEdit.variables); err != nil {
695 i.err = err
696 return false
697 }
698
699 commentEdits := i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits
700 // this is not supposed to happen
701 if len(commentEdits.Nodes) == 0 {
702 i.timeline.commentEdit.index = -1
703 return false
704 }
705
706 reverseEdits(commentEdits.Nodes)
707
708 i.commentEdit.index = 0
709 i.timeline.commentEdit.index = -2
710 return i.nextValidCommentEdit()
711}
712
713func (i *iterator) nextValidCommentEdit() bool {
714 // if comment edit diff is a nil pointer or points to an empty string look for next value
715 if commentEdit := i.CommentEditValue(); commentEdit.Diff == nil || string(*commentEdit.Diff) == "" {
716 return i.NextCommentEdit()
717 }
718 return true
719}
720
721// NextCommentEdit return true if there is a next comment edit and increments the index by one.
722// It is used iterates over all the comment edits. Extra queries are made if it is necessary.
723func (i *iterator) NextCommentEdit() bool {
724 if i.err != nil {
725 return false
726 }
727
728 if i.ctx.Err() != nil {
729 return false
730 }
731
732 // same as NextIssueEdit
733 if i.timeline.commentEdit.index == -2 {
734 commentEdits := i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits
735 if i.commentEdit.index < len(commentEdits.Nodes)-1 {
736 i.commentEdit.index++
737 return i.nextValidCommentEdit()
738 }
739
740 if !commentEdits.PageInfo.HasPreviousPage {
741 i.timeline.commentEdit.index = -1
742 i.commentEdit.index = -1
743 return false
744 }
745
746 // if there is more comment edits, query them
747 i.commentEdit.variables["commentEditBefore"] = commentEdits.PageInfo.StartCursor
748 return i.queryCommentEdit()
749 }
750
751 commentEdits := i.timeline.query.Repository.Issues.Nodes[0].TimelineItems.Edges[i.timeline.index].Node.IssueComment
752 // if there is no comment edits
753 if len(commentEdits.UserContentEdits.Nodes) == 0 {
754 return false
755 }
756
757 // loop over them timeline comment edits
758 if i.timeline.commentEdit.index < len(commentEdits.UserContentEdits.Nodes)-1 {
759 i.timeline.commentEdit.index++
760 return i.nextValidCommentEdit()
761 }
762
763 if !commentEdits.UserContentEdits.PageInfo.HasPreviousPage {
764 i.timeline.commentEdit.index = -1
765 return false
766 }
767
768 i.initCommentEditQueryVariables()
769 if i.timeline.index == 0 {
770 i.commentEdit.variables["timelineAfter"] = i.timeline.lastEndCursor
771 } else {
772 i.commentEdit.variables["timelineAfter"] = i.timeline.query.Repository.Issues.Nodes[0].TimelineItems.Edges[i.timeline.index-1].Cursor
773 }
774
775 i.commentEdit.variables["commentEditBefore"] = commentEdits.UserContentEdits.PageInfo.StartCursor
776
777 return i.queryCommentEdit()
778}
779
780// CommentEditValue return the actual comment edit value
781func (i *iterator) CommentEditValue() userContentEdit {
782 if i.timeline.commentEdit.index == -2 {
783 return i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.Nodes[i.commentEdit.index]
784 }
785
786 return i.timeline.query.Repository.Issues.Nodes[0].TimelineItems.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.Nodes[i.timeline.commentEdit.index]
787}
788
789func reverseEdits(edits []userContentEdit) {
790 for i, j := 0, len(edits)-1; i < j; i, j = i+1, j-1 {
791 edits[i], edits[j] = edits[j], edits[i]
792 }
793}