iterator.go

  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}