fbe 0.39.0 → 0.40.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bcdd3a7dc6452b707cd14157c1009b9b7a1f551f9ea66c5cc31c08849e87a2d3
4
- data.tar.gz: 2d1b97f86162a5e853df3a19e8ba6c4f96f398c51c19aa5a2bab0ee036cba1b4
3
+ metadata.gz: d19c0535556f2bc17f04be6cd25be80552d3016e22d1abc52ad867425e29cd31
4
+ data.tar.gz: 9a9dc13820984a088462dae32ce4b1c3adeb236290770a56529ee9aa1308405e
5
5
  SHA512:
6
- metadata.gz: a8e6f7be8faebeaa3aa2ad93f809988ef7fba01265faa67da6bf0c292cf088f1a655305820b1a72570dcd2282a065a1f6570daa349c0ea26458beed96a3d0845
7
- data.tar.gz: 4c2747bdf3bee54e7ecdf667db8d55f93323fc4448f1398753200b963404d5723e094f34064461787999456fa1316a551f470890dc7c6569310f28ccd8338c68
6
+ metadata.gz: d81b8b123cd43763746e53d02f62b9f2430fb9ab0a21e70d4f6250ca499ac5a3fc59775ddfbd37138fadcc8c7d51f0cc8a724e4923c3c9837deecd022d3c789b
7
+ data.tar.gz: 63e1fd591916726362b7fdc68c2153f57f3e2a22c5db01b4d280262d6dd3fbbf86b573bc612c2e4c3fb51b8e0211f7c89e910f0b85bdb59bf1edfa4913f3e7ab
@@ -16,6 +16,6 @@ jobs:
16
16
  runs-on: ubuntu-24.04
17
17
  steps:
18
18
  - uses: actions/checkout@v5
19
- - uses: crate-ci/typos@v1.36.3
19
+ - uses: crate-ci/typos@v1.37.2
20
20
  with:
21
21
  config: .github/.typos.toml
data/lib/fbe/conclude.rb CHANGED
@@ -206,7 +206,7 @@ class Fbe::Conclude
206
206
  passed = 0
207
207
  oct = Fbe.octo(loog: @loog, options: @options, global: @global)
208
208
  @fb.query(@query).each do |a|
209
- if @quota_aware && oct.off_quota?
209
+ if @quota_aware && oct.off_quota?(threshold: 100)
210
210
  @loog.info('We ran out of GitHub quota, must stop here')
211
211
  break
212
212
  end
@@ -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
@@ -249,6 +249,7 @@ class Fbe::Iterate
249
249
  repos = Fbe.unmask_repos(
250
250
  loog: @loog, options: @options, global: @global, quota_aware: @quota_aware
251
251
  ).map { |n| oct.repo_id_by_name(n) }
252
+ started = Time.now
252
253
  restarted = []
253
254
  before =
254
255
  repos.to_h do |repo|
@@ -266,7 +267,7 @@ class Fbe::Iterate
266
267
  starts = before.dup
267
268
  values = {}
268
269
  loop do
269
- if @quota_aware && oct.off_quota?
270
+ if @quota_aware && oct.off_quota?(threshold: 100)
270
271
  @loog.info("We are off GitHub quota, time to stop after #{started.ago}")
271
272
  break
272
273
  end
@@ -279,7 +280,7 @@ class Fbe::Iterate
279
280
  break
280
281
  end
281
282
  repos.each do |repo|
282
- if @quota_aware && oct.off_quota?
283
+ if @quota_aware && oct.off_quota?(threshold: 100)
283
284
  @loog.info("We are off GitHub quota, we must skip #{repo}")
284
285
  break
285
286
  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.rb CHANGED
@@ -10,5 +10,5 @@
10
10
  # License:: MIT
11
11
  module Fbe
12
12
  # Current version of the gem (changed by +.rultor.yml+ on every release)
13
- VERSION = '0.39.0' unless const_defined?(:VERSION)
13
+ VERSION = '0.40.0' unless const_defined?(:VERSION)
14
14
  end
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.39.0
4
+ version: 0.40.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko