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