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