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