fbe 0.39.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/reuse.yml +1 -1
- data/.github/workflows/typos.yml +1 -1
- data/lib/fbe/conclude.rb +12 -21
- data/lib/fbe/github_graph.rb +167 -0
- data/lib/fbe/iterate.rb +27 -30
- data/lib/fbe/octo.rb +4 -1
- data/lib/fbe/over.rb +38 -0
- data/lib/fbe.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e140ff5523bde9171719f3481941dfdf36b381696f313c98b7bb570b172ec49
|
4
|
+
data.tar.gz: a0fa3c06b03e0d0de48ab5e00e9d7985e933b68d174f0625b6a5b9388d2d980a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1fdf1f6e32990d2f34dd33337b4b6f73f0e423092c09834bce6d216746d5c387eb61befa3786c907a84c37e75dd00a0058ff8816b4b7ddc9de09ed166564e55
|
7
|
+
data.tar.gz: 5b7a3331b84db4c7b2b36b700e132987be7dd235b86b97ee7f3d4032a43b30d0bdf0d7a907700726bf7ca8fafcd13fb35e03c895f35b0e32964e57064c2d1a57
|
data/.github/workflows/reuse.yml
CHANGED
data/.github/workflows/typos.yml
CHANGED
data/lib/fbe/conclude.rb
CHANGED
@@ -8,6 +8,7 @@ require_relative '../fbe'
|
|
8
8
|
require_relative 'fb'
|
9
9
|
require_relative 'if_absent'
|
10
10
|
require_relative 'octo'
|
11
|
+
require_relative 'over'
|
11
12
|
|
12
13
|
# Creates an instance of {Fbe::Conclude} and evals it with the block provided.
|
13
14
|
#
|
@@ -195,29 +196,16 @@ class Fbe::Conclude
|
|
195
196
|
# end
|
196
197
|
# end
|
197
198
|
def roll(&)
|
198
|
-
|
199
|
-
@
|
200
|
-
|
201
|
-
|
202
|
-
if @timeout_aware && @options.timeout && Time.now - @kickoff > @options.timeout * 0.9
|
203
|
-
@loog.debug("We've spent more than #{@kickoff.ago}, won't even start")
|
204
|
-
return
|
205
|
-
end
|
199
|
+
return 0 if Fbe.over?(
|
200
|
+
global: @global, options: @options, loog: @loog, epoch: @epoch, kickoff: @kickoff,
|
201
|
+
quota_aware: @quota_aware, lifetime_aware: @lifetime_aware, timeout_aware: @timeout_aware
|
202
|
+
)
|
206
203
|
passed = 0
|
207
|
-
oct = Fbe.octo(loog: @loog, options: @options, global: @global)
|
208
204
|
@fb.query(@query).each do |a|
|
209
|
-
if
|
210
|
-
@
|
211
|
-
|
212
|
-
|
213
|
-
if @lifetime_aware && @options.lifetime && Time.now - @epoch > @options.lifetime * 0.9
|
214
|
-
@loog.debug("We ran out of lifetime (#{@epoch.ago} already), must stop here")
|
215
|
-
break
|
216
|
-
end
|
217
|
-
if @timeout_aware && @options.timeout && Time.now - @kickoff > @options.timeout * 0.9
|
218
|
-
@loog.debug("We've spent more than #{@kickoff.ago}, must stop here")
|
219
|
-
break
|
220
|
-
end
|
205
|
+
break if Fbe.over?(
|
206
|
+
global: @global, options: @options, loog: @loog, epoch: @epoch, kickoff: @kickoff,
|
207
|
+
quota_aware: @quota_aware, lifetime_aware: @lifetime_aware, timeout_aware: @timeout_aware
|
208
|
+
)
|
221
209
|
@fb.txn do |fbt|
|
222
210
|
n = yield fbt, a
|
223
211
|
@loog.info("#{n.what}: #{n.details}") unless n.nil?
|
@@ -226,6 +214,9 @@ class Fbe::Conclude
|
|
226
214
|
end
|
227
215
|
@loog.debug("Found and processed #{passed} facts by: #{@query}")
|
228
216
|
passed
|
217
|
+
rescue Fbe::OffQuota => e
|
218
|
+
@loog.info(e.message)
|
219
|
+
passed
|
229
220
|
end
|
230
221
|
|
231
222
|
# Populates a new fact based on a previous fact and a processing block.
|
data/lib/fbe/github_graph.rb
CHANGED
@@ -232,6 +232,132 @@ class Fbe::Graph
|
|
232
232
|
}
|
233
233
|
end
|
234
234
|
|
235
|
+
# Get pulls id and number with review from since
|
236
|
+
#
|
237
|
+
# @param [String] owner The repository owner (username or organization)
|
238
|
+
# @param [String] name The repository name
|
239
|
+
# @param [Time] since The datetime from
|
240
|
+
# @param [String, nil] cursor Github cursor for next page
|
241
|
+
# @return [Hash] A hash with pulls
|
242
|
+
# @example
|
243
|
+
# graph = Fbe::Graph.new(token: 'github_token')
|
244
|
+
# cursor = nil
|
245
|
+
# pulls = []
|
246
|
+
# loop do
|
247
|
+
# json = graph.pull_requests_with_reviews(
|
248
|
+
# 'zerocracy', 'judges-action', Time.parse('2025-08-01T18:00:00Z'), cursor:
|
249
|
+
# )
|
250
|
+
# json['pulls_with_reviews'].each do |p|
|
251
|
+
# pulls.push(p['number'])
|
252
|
+
# end
|
253
|
+
# break unless json['has_next_page']
|
254
|
+
# cursor = json['next_cursor']
|
255
|
+
# end
|
256
|
+
def pull_requests_with_reviews(owner, name, since, cursor: nil)
|
257
|
+
result = query(
|
258
|
+
<<~GRAPHQL
|
259
|
+
{
|
260
|
+
repository(owner: "#{owner}", name: "#{name}") {
|
261
|
+
pullRequests(first: 100, after: "#{cursor}") {
|
262
|
+
nodes {
|
263
|
+
id
|
264
|
+
number
|
265
|
+
timelineItems(first: 1, itemTypes: [PULL_REQUEST_REVIEW], since: "#{since.utc.iso8601}") {
|
266
|
+
nodes {
|
267
|
+
... on PullRequestReview { id }
|
268
|
+
}
|
269
|
+
}
|
270
|
+
}
|
271
|
+
pageInfo {
|
272
|
+
hasNextPage
|
273
|
+
endCursor
|
274
|
+
}
|
275
|
+
}
|
276
|
+
}
|
277
|
+
}
|
278
|
+
GRAPHQL
|
279
|
+
).to_h
|
280
|
+
{
|
281
|
+
'pulls_with_reviews' => result
|
282
|
+
.dig('repository', 'pullRequests', 'nodes')
|
283
|
+
.reject { _1.dig('timelineItems', 'nodes').empty? }
|
284
|
+
.map do |pull|
|
285
|
+
{
|
286
|
+
'id' => pull['id'],
|
287
|
+
'number' => pull['number']
|
288
|
+
}
|
289
|
+
end,
|
290
|
+
'has_next_page' => result.dig('repository', 'pullRequests', 'pageInfo', 'hasNextPage'),
|
291
|
+
'next_cursor' => result.dig('repository', 'pullRequests', 'pageInfo', 'endCursor')
|
292
|
+
}
|
293
|
+
end
|
294
|
+
|
295
|
+
# Get reviews by pull numbers
|
296
|
+
#
|
297
|
+
# @param [String] owner The repository owner (username or organization)
|
298
|
+
# @param [String] name The repository name
|
299
|
+
# @param [Array<Array<Integer, (String, nil)>>] pulls Array of pull number and Github cursor
|
300
|
+
# @return [Hash] A hash with reviews
|
301
|
+
# @example
|
302
|
+
# graph = Fbe::Graph.new(token: 'github_token')
|
303
|
+
# queue = [[1108, nil], [1105, nil]]
|
304
|
+
# until queue.empty?
|
305
|
+
# pulls = graph.pull_request_reviews('zerocracy', 'judges-action', pulls: queue.shift(10))
|
306
|
+
# pulls.each do |pull|
|
307
|
+
# puts pull['id'], pull['number']
|
308
|
+
# pull['reviews'].each do |r|
|
309
|
+
# puts r['id'], r['submitted_at']
|
310
|
+
# end
|
311
|
+
# end
|
312
|
+
# pulls.select { _1['reviews_has_next_page'] }.each do |p|
|
313
|
+
# queue.push([p['number'], p['reviews_next_cursor']])
|
314
|
+
# end
|
315
|
+
# end
|
316
|
+
def pull_request_reviews(owner, name, pulls: [])
|
317
|
+
requests =
|
318
|
+
pulls.map do |number, cursor|
|
319
|
+
<<~GRAPHQL
|
320
|
+
pr_#{number}: pullRequest(number: #{number}) {
|
321
|
+
id
|
322
|
+
number
|
323
|
+
reviews(first: 100, after: "#{cursor}") {
|
324
|
+
nodes {
|
325
|
+
id
|
326
|
+
submittedAt
|
327
|
+
}
|
328
|
+
pageInfo {
|
329
|
+
hasNextPage
|
330
|
+
endCursor
|
331
|
+
}
|
332
|
+
}
|
333
|
+
}
|
334
|
+
GRAPHQL
|
335
|
+
end
|
336
|
+
result = query(
|
337
|
+
<<~GRAPHQL
|
338
|
+
{
|
339
|
+
repository(owner: "#{owner}", name: "#{name}") {
|
340
|
+
#{requests.join("\n")}
|
341
|
+
}
|
342
|
+
}
|
343
|
+
GRAPHQL
|
344
|
+
).to_h
|
345
|
+
result['repository'].map do |_k, v|
|
346
|
+
{
|
347
|
+
'id' => v['id'],
|
348
|
+
'number' => v['number'],
|
349
|
+
'reviews' => v.dig('reviews', 'nodes').map do |r|
|
350
|
+
{
|
351
|
+
'id' => r['id'],
|
352
|
+
'submitted_at' => Time.parse(r['submittedAt'])
|
353
|
+
}
|
354
|
+
end,
|
355
|
+
'reviews_has_next_page' => v.dig('reviews', 'pageInfo', 'hasNextPage'),
|
356
|
+
'reviews_next_cursor' => v.dig('reviews', 'pageInfo', 'endCursor')
|
357
|
+
}
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
235
361
|
private
|
236
362
|
|
237
363
|
# Creates or returns a cached GraphQL client instance.
|
@@ -403,6 +529,47 @@ class Fbe::Graph
|
|
403
529
|
end
|
404
530
|
end
|
405
531
|
|
532
|
+
def pull_requests_with_reviews(_owner, _name, _since, **)
|
533
|
+
{
|
534
|
+
'pulls_with_reviews' => [
|
535
|
+
{ 'id' => 'PR_kwDOL6J6Ss6iprCx', 'number' => 2 },
|
536
|
+
{ 'id' => 'PR_kwDOL6J6Ss6rhJ7T', 'number' => 5 },
|
537
|
+
{ 'id' => 'PR_kwDOL6J6Ss6r13fG', 'number' => 21 }
|
538
|
+
],
|
539
|
+
'has_next_page' => false,
|
540
|
+
'next_cursor' => 'Y3Vyc29yOnYyOpHOdh_xUw=='
|
541
|
+
}
|
542
|
+
end
|
543
|
+
|
544
|
+
def pull_request_reviews(_owner, _name, **)
|
545
|
+
[
|
546
|
+
{
|
547
|
+
'id' => 'PR_kwDOL6J6Ss6iprCx',
|
548
|
+
'number' => 2,
|
549
|
+
'reviews' => [
|
550
|
+
{ 'id' => 'PRR_kwDOL6J6Ss647NCl', 'submitted_at' => Time.parse('2025-10-02 12:58:42 UTC') },
|
551
|
+
{ 'id' => 'PRR_kwDOL6J6Ss647NC8', 'submitted_at' => Time.parse('2025-10-02 15:58:42 UTC') }
|
552
|
+
],
|
553
|
+
'reviews_has_next_page' => false,
|
554
|
+
'reviews_next_cursor' => 'yc29yOnYyO1'
|
555
|
+
},
|
556
|
+
{
|
557
|
+
'id' => 'PR_kwDOL6J6Ss6rhJ7T',
|
558
|
+
'number' => 5,
|
559
|
+
'reviews' => [{ 'id' => 'PRR_kwDOL6J6Ss64_mnn', 'submitted_at' => Time.parse('2025-10-03 15:58:42 UTC') }],
|
560
|
+
'reviews_has_next_page' => false,
|
561
|
+
'reviews_next_cursor' => 'yc29yOnYyO2'
|
562
|
+
},
|
563
|
+
{
|
564
|
+
'id' => 'PR_kwDOL6J6Ss6r13fG',
|
565
|
+
'number' => 21,
|
566
|
+
'reviews' => [{ 'id' => 'PRR_kwDOL6J6Ss65AbIA', 'submitted_at' => Time.parse('2025-10-04 15:58:42 UTC') }],
|
567
|
+
'reviews_has_next_page' => false,
|
568
|
+
'reviews_next_cursor' => 'yc29yOnYyO3'
|
569
|
+
}
|
570
|
+
]
|
571
|
+
end
|
572
|
+
|
406
573
|
private
|
407
574
|
|
408
575
|
# Generates mock conversation thread data.
|
data/lib/fbe/iterate.rb
CHANGED
@@ -10,6 +10,7 @@ require_relative '../fbe'
|
|
10
10
|
require_relative 'fb'
|
11
11
|
require_relative 'if_absent'
|
12
12
|
require_relative 'octo'
|
13
|
+
require_relative 'over'
|
13
14
|
require_relative 'overwrite'
|
14
15
|
require_relative 'unmask_repos'
|
15
16
|
|
@@ -249,6 +250,7 @@ class Fbe::Iterate
|
|
249
250
|
repos = Fbe.unmask_repos(
|
250
251
|
loog: @loog, options: @options, global: @global, quota_aware: @quota_aware
|
251
252
|
).map { |n| oct.repo_id_by_name(n) }
|
253
|
+
started = Time.now
|
252
254
|
restarted = []
|
253
255
|
before =
|
254
256
|
repos.to_h do |repo|
|
@@ -266,29 +268,19 @@ class Fbe::Iterate
|
|
266
268
|
starts = before.dup
|
267
269
|
values = {}
|
268
270
|
loop do
|
269
|
-
if
|
270
|
-
@
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
@loog.debug("We ran out of lifetime (#{@epoch.ago} already), must stop here")
|
275
|
-
break
|
276
|
-
end
|
277
|
-
if @timeout_aware && @options.timeout && Time.now - @kickoff > @options.timeout * 0.9
|
278
|
-
@loog.debug("We've spent more than #{@kickoff.ago}, must stop here")
|
271
|
+
if Fbe.over?(
|
272
|
+
global: @global, options: @options, loog: @loog, epoch: @epoch, kickoff: @kickoff,
|
273
|
+
quota_aware: @quota_aware, lifetime_aware: @lifetime_aware, timeout_aware: @timeout_aware
|
274
|
+
)
|
275
|
+
@loog.info("Time to stop after #{started.ago}")
|
279
276
|
break
|
280
277
|
end
|
281
278
|
repos.each do |repo|
|
282
|
-
if
|
283
|
-
@
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
@loog.info("We are working for #{@epoch.ago} already, won't check repository ##{repo}")
|
288
|
-
next
|
289
|
-
end
|
290
|
-
if @timeout_aware && @options.timeout && Time.now - @kickoff > @options.timeout * 0.9
|
291
|
-
@loog.debug("We've spent more than #{@kickoff.ago}, won't check repository ##{repo}")
|
279
|
+
if Fbe.over?(
|
280
|
+
global: @global, options: @options, loog: @loog, epoch: @epoch, kickoff: @kickoff,
|
281
|
+
quota_aware: @quota_aware, lifetime_aware: @lifetime_aware, timeout_aware: @timeout_aware
|
282
|
+
)
|
283
|
+
@loog.info("Won't check repository ##{repo}")
|
292
284
|
break
|
293
285
|
end
|
294
286
|
next if restarted.include?(repo)
|
@@ -334,16 +326,21 @@ class Fbe::Iterate
|
|
334
326
|
break
|
335
327
|
end
|
336
328
|
end
|
337
|
-
repos.each do |repo|
|
338
|
-
next if before[repo] == starts[repo]
|
339
|
-
f =
|
340
|
-
Fbe.if_absent(fb: @fb, always: true) do |n|
|
341
|
-
n.what = 'iterate'
|
342
|
-
n.where = 'github'
|
343
|
-
n.repository = repo
|
344
|
-
end
|
345
|
-
Fbe.overwrite(f, @label, before[repo], fb: @fb)
|
346
|
-
end
|
347
329
|
@loog.debug("Finished scanning #{repos.size} repos in #{@kickoff.ago}: #{seen.map { |k, v| "#{k}:#{v}" }.joined}")
|
330
|
+
rescue Fbe::OffQuota => e
|
331
|
+
@loog.info(e.message)
|
332
|
+
ensure
|
333
|
+
if defined?(repos) && defined?(before) && defined?(starts)
|
334
|
+
repos.each do |repo|
|
335
|
+
next if before[repo] == starts[repo]
|
336
|
+
f =
|
337
|
+
Fbe.if_absent(fb: @fb, always: true) do |n|
|
338
|
+
n.what = 'iterate'
|
339
|
+
n.where = 'github'
|
340
|
+
n.repository = repo
|
341
|
+
end
|
342
|
+
Fbe.overwrite(f, @label, before[repo], fb: @fb)
|
343
|
+
end
|
344
|
+
end
|
348
345
|
end
|
349
346
|
end
|
data/lib/fbe/octo.rb
CHANGED
@@ -24,6 +24,9 @@ require_relative 'middleware/rate_limit'
|
|
24
24
|
require_relative 'middleware/sqlite_store'
|
25
25
|
require_relative 'middleware/trace'
|
26
26
|
|
27
|
+
# When we are off quota.
|
28
|
+
class Fbe::OffQuota < StandardError; end
|
29
|
+
|
27
30
|
# Makes a call to the GitHub API.
|
28
31
|
#
|
29
32
|
# It is supposed to be used instead of +Octokit::Client+, because it
|
@@ -221,7 +224,7 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
221
224
|
o =
|
222
225
|
intercepted(o) do |e, m, _args, _r|
|
223
226
|
if e == :before && m != :off_quota? && m != :print_trace! && m != :rate_limit && o.off_quota?
|
224
|
-
raise "We are off-quota (remaining: #{o.rate_limit.remaining}), can't do #{m}()"
|
227
|
+
raise Fbe::OffQuota, "We are off-quota (remaining: #{o.rate_limit.remaining}), can't do #{m}()"
|
225
228
|
end
|
226
229
|
end
|
227
230
|
o
|
data/lib/fbe/over.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
|
4
|
+
# SPDX-License-Identifier: MIT
|
5
|
+
|
6
|
+
require_relative '../fbe'
|
7
|
+
require_relative 'octo'
|
8
|
+
|
9
|
+
# Check GitHub API quota, lifetime, and timeout.
|
10
|
+
#
|
11
|
+
# @param [Hash] global Hash of global options
|
12
|
+
# @param [Judges::Options] options The options available globally
|
13
|
+
# @param [Loog] loog Logging facility
|
14
|
+
# @param [Time] epoch When the entire update started
|
15
|
+
# @param [Time] kickoff When the particular judge started
|
16
|
+
# @param [Boolean] quota_aware Enable or disable check of GitHub API quota
|
17
|
+
# @param [Boolean] lifetime_aware Enable or disable check of lifetime limitations
|
18
|
+
# @param [Boolean] timeout_aware Enable or disable check of timeout limitations
|
19
|
+
# @return [Boolean] check result
|
20
|
+
def Fbe.over?(
|
21
|
+
global: $global, options: $options, loog: $loog,
|
22
|
+
epoch: $epoch || Time.now, kickoff: $kickoff || Time.now,
|
23
|
+
quota_aware: true, lifetime_aware: true, timeout_aware: true
|
24
|
+
)
|
25
|
+
if quota_aware && Fbe.octo(loog:, options:, global:).off_quota?(threshold: 100)
|
26
|
+
loog.info('We are off GitHub quota, time to stop')
|
27
|
+
return true
|
28
|
+
end
|
29
|
+
if lifetime_aware && options.lifetime && Time.now - epoch > options.lifetime * 0.9
|
30
|
+
loog.info("We ran out of lifetime (#{epoch.ago} already), must stop here")
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
if timeout_aware && options.timeout && Time.now - kickoff > options.timeout * 0.9
|
34
|
+
loog.info("We've spent more than #{kickoff.ago}, must stop here")
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
false
|
38
|
+
end
|
data/lib/fbe.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fbe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.41.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
@@ -384,6 +384,7 @@ files:
|
|
384
384
|
- lib/fbe/middleware/sqlite_store.rb
|
385
385
|
- lib/fbe/middleware/trace.rb
|
386
386
|
- lib/fbe/octo.rb
|
387
|
+
- lib/fbe/over.rb
|
387
388
|
- lib/fbe/overwrite.rb
|
388
389
|
- lib/fbe/pmp.rb
|
389
390
|
- lib/fbe/regularly.rb
|