fbe 0.8.1 → 0.10.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/.rubocop.yml +0 -1
- data/Gemfile.lock +8 -8
- data/assets/bylaws/bug-report-was-rewarded.liquid +1 -1
- data/assets/bylaws/code-contribution-was-rewarded.liquid +11 -4
- data/assets/bylaws/code-review-was-rewarded.liquid +1 -1
- data/assets/bylaws/enhancement-suggestion-was-rewarded.liquid +1 -1
- data/assets/bylaws/resolved-bug-was-rewarded.liquid +1 -1
- data/lib/fbe/award.rb +149 -5
- data/lib/fbe/conclude.rb +39 -2
- data/lib/fbe/github_graph.rb +52 -1
- data/lib/fbe/middleware/formatter.rb +14 -0
- data/lib/fbe/octo.rb +57 -4
- data/lib/fbe/pmp.rb +17 -1
- data/lib/fbe.rb +1 -1
- data/test/fbe/test_bylaws.rb +13 -12
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50140dbca73e4022726fad153402221b92d01a94f1cfc22eeb8f382170f0b4bc
|
4
|
+
data.tar.gz: 8fc5d55bc4b46f57dadb0bb36bd5461f2253bef19e143fb15704263e64d0ccd8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce99b5a1ad7500b952a731a863497b50b0f7b553a1b4521202149f1878d8b9e8eef8af79438d7ab96cb8ac238182a6ad044a98e82677efbc439d4a5ce39a6bfd
|
7
|
+
data.tar.gz: d51c72a5299c03aeb1e1bf20839a94e3d1c05fc679c6d32c8959d8634d2a9cdfa080082950358e33ba62bda4242dd8231f11bbb2a7f7752165c9fd5f7e04f5e6
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -58,7 +58,7 @@ GEM
|
|
58
58
|
bigdecimal (3.1.9)
|
59
59
|
builder (3.3.0)
|
60
60
|
concurrent-ruby (1.3.5)
|
61
|
-
connection_pool (2.5.
|
61
|
+
connection_pool (2.5.1)
|
62
62
|
crack (1.0.0)
|
63
63
|
bigdecimal
|
64
64
|
rexml
|
@@ -99,7 +99,7 @@ GEM
|
|
99
99
|
fiber-storage (1.0.1)
|
100
100
|
gli (2.22.2)
|
101
101
|
ostruct
|
102
|
-
graphql (2.5.
|
102
|
+
graphql (2.5.4)
|
103
103
|
base64
|
104
104
|
fiber-storage
|
105
105
|
logger
|
@@ -111,7 +111,7 @@ GEM
|
|
111
111
|
concurrent-ruby (~> 1.0)
|
112
112
|
iri (0.10.0)
|
113
113
|
json (2.10.2)
|
114
|
-
judges (0.
|
114
|
+
judges (0.41.0)
|
115
115
|
backtrace (~> 0)
|
116
116
|
baza.rb (~> 0)
|
117
117
|
concurrent-ruby (~> 1.2)
|
@@ -143,13 +143,13 @@ GEM
|
|
143
143
|
multipart-post (2.4.1)
|
144
144
|
net-http (0.6.0)
|
145
145
|
uri
|
146
|
-
nokogiri (1.18.
|
146
|
+
nokogiri (1.18.8-arm64-darwin)
|
147
147
|
racc (~> 1.4)
|
148
|
-
nokogiri (1.18.
|
148
|
+
nokogiri (1.18.8-x64-mingw-ucrt)
|
149
149
|
racc (~> 1.4)
|
150
|
-
nokogiri (1.18.
|
150
|
+
nokogiri (1.18.8-x86_64-darwin)
|
151
151
|
racc (~> 1.4)
|
152
|
-
nokogiri (1.18.
|
152
|
+
nokogiri (1.18.8-x86_64-linux-gnu)
|
153
153
|
racc (~> 1.4)
|
154
154
|
obk (0.3.2)
|
155
155
|
octokit (9.2.0)
|
@@ -175,7 +175,7 @@ GEM
|
|
175
175
|
regexp_parser (2.10.0)
|
176
176
|
retries (0.0.5)
|
177
177
|
rexml (3.4.1)
|
178
|
-
rubocop (1.75.
|
178
|
+
rubocop (1.75.4)
|
179
179
|
json (~> 2.3)
|
180
180
|
language_server-protocol (~> 3.17.0.2)
|
181
181
|
lint_roller (~> 1.1.0)
|
@@ -5,7 +5,7 @@
|
|
5
5
|
(in reviews "the number of reviews provided")
|
6
6
|
|
7
7
|
(aka
|
8
|
-
(let basis {{
|
8
|
+
(let basis {{ 8 | times: love }})
|
9
9
|
(give basis "as a basis")
|
10
10
|
"award ${basis} points")
|
11
11
|
|
@@ -46,10 +46,17 @@
|
|
46
46
|
(let comments_k {{ 0.1 | times: anger }})
|
47
47
|
(let comments_max -16)
|
48
48
|
(let comments_min -4)
|
49
|
-
(
|
49
|
+
(let comments_threshold {{ 16 | divided_by: paranoia }})
|
50
|
+
(set bonus_for_comments
|
51
|
+
(if
|
52
|
+
(gt comments comments_threshold)
|
53
|
+
(times comments (times -1 comments_k))
|
54
|
+
0
|
55
|
+
)
|
56
|
+
)
|
50
57
|
(set bonus_for_comments (between bonus_for_comments comments_min comments_max))
|
51
|
-
(give bonus_for_comments "for too many (${comments}) comments that were made during review")
|
52
|
-
"deduct ${comments_k} points for every comment made during review, but not more than ${comments_max} points")
|
58
|
+
(give bonus_for_comments "for too many (${comments} > ${comments_threshold}) comments that were made during review")
|
59
|
+
"deduct ${comments_k} points for every comment above ${comments_threshold} made during review, but not more than ${comments_max} points")
|
53
60
|
|
54
61
|
(aka
|
55
62
|
(let few_hoc_fee {{ 4 | times: anger }})
|
data/lib/fbe/award.rb
CHANGED
@@ -62,20 +62,46 @@ class Fbe::Award
|
|
62
62
|
bylaw
|
63
63
|
end
|
64
64
|
|
65
|
-
# A term for
|
65
|
+
# A term module for processing award billing logic.
|
66
|
+
#
|
67
|
+
# This module is used to extend Factbase terms to handle award calculations
|
68
|
+
# and billing operations. It provides methods to calculate point values and
|
69
|
+
# evaluate complex award expressions.
|
66
70
|
module BTerm
|
71
|
+
# Returns a string representation of the term.
|
72
|
+
#
|
73
|
+
# @return [String] The term as a string in S-expression format
|
74
|
+
# @example
|
75
|
+
# term.to_s #=> "(give (times loc 5) 'for LoC')"
|
67
76
|
def to_s
|
68
77
|
"(#{@op} #{@operands.join(' ')})"
|
69
78
|
end
|
70
79
|
|
80
|
+
# Indicates whether the term is static.
|
81
|
+
#
|
82
|
+
# @return [Boolean] Always returns true for BTerm
|
71
83
|
def static?
|
72
84
|
true
|
73
85
|
end
|
74
86
|
|
87
|
+
# Indicates whether the term is abstract.
|
88
|
+
#
|
89
|
+
# @return [Boolean] Always returns false for BTerm
|
75
90
|
def abstract?
|
76
91
|
false
|
77
92
|
end
|
78
93
|
|
94
|
+
# Processes this term and applies its operations to a bill.
|
95
|
+
#
|
96
|
+
# @param [Fbe::Award::Bill] bill The bill to update
|
97
|
+
# @return [nil]
|
98
|
+
# @raise [RuntimeError] If there's a failure processing any term
|
99
|
+
# @example
|
100
|
+
# term = Factbase::Syntax.new('(award (give 100 "for effort"))').to_term
|
101
|
+
# term.redress!(Fbe::Award::BTerm)
|
102
|
+
# bill = Fbe::Award::Bill.new
|
103
|
+
# term.bill_to(bill)
|
104
|
+
# bill.points #=> 100
|
79
105
|
def bill_to(bill)
|
80
106
|
case @op
|
81
107
|
when :award
|
@@ -91,7 +117,9 @@ class Fbe::Award
|
|
91
117
|
raise "Failure in #{o}: #{e.message}"
|
92
118
|
end
|
93
119
|
when :let, :set
|
94
|
-
|
120
|
+
v = to_val(@operands[1], bill)
|
121
|
+
raise "Can't #{@op.inspect} #{@operands[0].inspect} to nil" if v.nil?
|
122
|
+
bill.set(@operands[0], v)
|
95
123
|
when :give
|
96
124
|
text = @operands[1]
|
97
125
|
text = '' if text.nil?
|
@@ -103,18 +131,43 @@ class Fbe::Award
|
|
103
131
|
end
|
104
132
|
end
|
105
133
|
|
134
|
+
# Evaluates a value in the context of a bill.
|
135
|
+
#
|
136
|
+
# @param [Object] any The value to evaluate (symbol, term, or literal)
|
137
|
+
# @param [Fbe::Award::Bill] bill The bill providing context for evaluation
|
138
|
+
# @return [Object] The evaluated value
|
139
|
+
# @raise [RuntimeError] If a symbol isn't found in the bill
|
140
|
+
# @example
|
141
|
+
# bill = Fbe::Award::Bill.new
|
142
|
+
# bill.set(:loc, 100)
|
143
|
+
# term.to_val(:loc, bill) #=> 100
|
106
144
|
def to_val(any, bill)
|
107
145
|
if any.is_a?(BTerm)
|
108
146
|
any.calc(bill)
|
109
147
|
elsif any.is_a?(Symbol)
|
110
148
|
v = bill.vars[any]
|
111
|
-
raise "Unknown name
|
149
|
+
raise "Unknown name #{any.inspect} among: #{bill.vars.keys.map(&:inspect).join(', ')}" if v.nil?
|
112
150
|
v
|
113
151
|
else
|
114
152
|
any
|
115
153
|
end
|
116
154
|
end
|
117
155
|
|
156
|
+
# Calculates the value of this term in the context of a bill.
|
157
|
+
#
|
158
|
+
# This method evaluates terms like arithmetic operations, logical
|
159
|
+
# operations, and other expressions based on the operator type.
|
160
|
+
#
|
161
|
+
# @param [Fbe::Award::Bill] bill The bill providing context for calculation
|
162
|
+
# @return [Object] The calculated value (number, boolean, etc.)
|
163
|
+
# @raise [RuntimeError] If the term operation is unknown
|
164
|
+
# @example
|
165
|
+
# bill = Fbe::Award::Bill.new
|
166
|
+
# bill.set(:x, 10)
|
167
|
+
# bill.set(:y, 5)
|
168
|
+
# term = Factbase::Syntax.new('(times x y)').to_term
|
169
|
+
# term.redress!(Fbe::Award::BTerm)
|
170
|
+
# term.calc(bill) #=> 50
|
118
171
|
def calc(bill)
|
119
172
|
case @op
|
120
173
|
when :total
|
@@ -273,29 +326,70 @@ class Fbe::Award
|
|
273
326
|
end
|
274
327
|
end
|
275
328
|
|
276
|
-
# A bill.
|
329
|
+
# A bill class that accumulates points and explanations for rewards.
|
330
|
+
#
|
331
|
+
# This class tracks variables, point values, and explanatory text
|
332
|
+
# for each award component. It provides methods to calculate total points
|
333
|
+
# and generate a human-readable summary of the rewards.
|
277
334
|
class Bill
|
335
|
+
# @return [Hash] Variables set in this bill
|
278
336
|
attr_reader :vars
|
279
337
|
|
338
|
+
# Creates a new empty bill.
|
339
|
+
#
|
340
|
+
# @example
|
341
|
+
# bill = Fbe::Award::Bill.new
|
280
342
|
def initialize
|
281
343
|
@lines = []
|
282
344
|
@vars = {}
|
283
345
|
end
|
284
346
|
|
347
|
+
# Sets a variable in the bill's context.
|
348
|
+
#
|
349
|
+
# @param [Symbol] var The variable name
|
350
|
+
# @param [Object] value The value to assign
|
351
|
+
# @return [Object] The assigned value
|
352
|
+
# @example
|
353
|
+
# bill = Fbe::Award::Bill.new
|
354
|
+
# bill.set(:lines_of_code, 500)
|
285
355
|
def set(var, value)
|
286
356
|
@vars[var] = value
|
287
357
|
end
|
288
358
|
|
359
|
+
# Adds a point value with explanatory text to the bill.
|
360
|
+
#
|
361
|
+
# @param [Integer, Float] value The point value to add
|
362
|
+
# @param [String] text The explanation for these points
|
363
|
+
# @return [nil]
|
364
|
+
# @note Zero-valued points are ignored
|
365
|
+
# @example
|
366
|
+
# bill = Fbe::Award::Bill.new
|
367
|
+
# bill.line(50, "for code review")
|
289
368
|
def line(value, text)
|
290
369
|
return if value.zero?
|
291
370
|
text = text.gsub(/\$\{([a-z_0-9]+)\}/) { |_x| @vars[Regexp.last_match[1].to_sym] }
|
292
371
|
@lines << { v: value, t: text }
|
293
372
|
end
|
294
373
|
|
374
|
+
# Calculates the total points in this bill.
|
375
|
+
#
|
376
|
+
# @return [Integer] The sum of all point values, rounded to an integer
|
377
|
+
# @example
|
378
|
+
# bill = Fbe::Award::Bill.new
|
379
|
+
# bill.line(42.5, "for answer")
|
380
|
+
# bill.points #=> 43
|
295
381
|
def points
|
296
382
|
@lines.sum { |l| l[:v] }.to_f.round.to_i
|
297
383
|
end
|
298
384
|
|
385
|
+
# Generates a human-readable summary of the bill.
|
386
|
+
#
|
387
|
+
# @return [String] A description of the points earned
|
388
|
+
# @example
|
389
|
+
# bill = Fbe::Award::Bill.new
|
390
|
+
# bill.line(50, "for code review")
|
391
|
+
# bill.line(25, "for documentation")
|
392
|
+
# bill.greeting #=> "You've earned +75 points for this: +50 for code review; +25 for documentation. "
|
299
393
|
def greeting
|
300
394
|
items = @lines.map { |l| "#{format('%+d', l[:v])} #{l[:t]}" }
|
301
395
|
case items.size
|
@@ -309,33 +403,83 @@ class Fbe::Award
|
|
309
403
|
end
|
310
404
|
end
|
311
405
|
|
312
|
-
# A
|
406
|
+
# A class for generating human-readable bylaws.
|
407
|
+
#
|
408
|
+
# This class builds textual descriptions of award bylaws including
|
409
|
+
# introductions, calculation steps, and variable substitutions.
|
410
|
+
# It produces Markdown-formatted output describing how awards are calculated.
|
313
411
|
class Bylaw
|
412
|
+
# @return [Hash] Variables defined in this bylaw
|
314
413
|
attr_reader :vars
|
315
414
|
|
415
|
+
# Creates a new empty bylaw.
|
416
|
+
#
|
417
|
+
# @example
|
418
|
+
# bylaw = Fbe::Award::Bylaw.new
|
316
419
|
def initialize
|
317
420
|
@lines = []
|
318
421
|
@intro = ''
|
319
422
|
@lets = {}
|
320
423
|
end
|
321
424
|
|
425
|
+
# Removes the specified number of most recently added lines.
|
426
|
+
#
|
427
|
+
# @param [Integer] num The number of lines to remove from the end
|
428
|
+
# @return [Array] The removed lines
|
429
|
+
# @example
|
430
|
+
# bylaw = Fbe::Award::Bylaw.new
|
431
|
+
# bylaw.line("award 50 points")
|
432
|
+
# bylaw.line("award 30 points")
|
433
|
+
# bylaw.revert(1) # Removes "award 30 points"
|
322
434
|
def revert(num)
|
323
435
|
@lines.slice!(-num, num)
|
324
436
|
end
|
325
437
|
|
438
|
+
# Sets the introductory text for the bylaw.
|
439
|
+
#
|
440
|
+
# @param [String] text The introductory text to set
|
441
|
+
# @return [String] The introductory text
|
442
|
+
# @example
|
443
|
+
# bylaw = Fbe::Award::Bylaw.new
|
444
|
+
# bylaw.intro("This bylaw determines rewards for code contributions")
|
326
445
|
def intro(text)
|
327
446
|
@intro = text
|
328
447
|
end
|
329
448
|
|
449
|
+
# Adds a line of text to the bylaw, replacing variable references.
|
450
|
+
#
|
451
|
+
# @param [String] line The line of text to add
|
452
|
+
# @return [nil]
|
453
|
+
# @example
|
454
|
+
# bylaw = Fbe::Award::Bylaw.new
|
455
|
+
# bylaw.let(:points, 50)
|
456
|
+
# bylaw.line("award ${points} points")
|
330
457
|
def line(line)
|
331
458
|
line = line.gsub(/\$\{([a-z_0-9]+)\}/) { |_x| "**#{@lets[Regexp.last_match[1].to_sym]}**" }
|
332
459
|
@lines << line
|
333
460
|
end
|
334
461
|
|
462
|
+
# Registers a variable with its value for substitution in lines.
|
463
|
+
#
|
464
|
+
# @param [Symbol] key The variable name
|
465
|
+
# @param [Object] value The value to associate with the variable
|
466
|
+
# @return [Object] The assigned value
|
467
|
+
# @example
|
468
|
+
# bylaw = Fbe::Award::Bylaw.new
|
469
|
+
# bylaw.let(:points, 50)
|
335
470
|
def let(key, value)
|
336
471
|
@lets[key] = value
|
337
472
|
end
|
338
473
|
|
474
|
+
# Generates a Markdown-formatted representation of the bylaw.
|
475
|
+
#
|
476
|
+
# @return [String] The bylaw formatted as Markdown text
|
477
|
+
# @example
|
478
|
+
# bylaw = Fbe::Award::Bylaw.new
|
479
|
+
# bylaw.intro("This bylaw determines rewards for code contributions")
|
480
|
+
# bylaw.line("award **50** points")
|
481
|
+
# bylaw.markdown
|
482
|
+
# #=> "This bylaw determines rewards for code contributions. Just award **50** points."
|
339
483
|
def markdown
|
340
484
|
pars = []
|
341
485
|
pars << "#{@intro}." unless @intro.empty?
|
data/lib/fbe/conclude.rb
CHANGED
@@ -160,8 +160,24 @@ class Fbe::Conclude
|
|
160
160
|
|
161
161
|
private
|
162
162
|
|
163
|
-
#
|
164
|
-
#
|
163
|
+
# Executes a query and processes each matching fact.
|
164
|
+
#
|
165
|
+
# This internal method handles fetching facts from the factbase,
|
166
|
+
# monitoring quotas and timeouts, and processing each fact through
|
167
|
+
# the provided block.
|
168
|
+
#
|
169
|
+
# @yield [Factbase::Transaction, Factbase::Fact] Transaction and the matching fact
|
170
|
+
# @return [Integer] The count of facts processed
|
171
|
+
# @example
|
172
|
+
# # Inside the Fbe::Conclude class
|
173
|
+
# def example_method
|
174
|
+
# roll do |fbt, fact|
|
175
|
+
# # Process the fact
|
176
|
+
# new_fact = fbt.insert
|
177
|
+
# # Return the new fact
|
178
|
+
# new_fact
|
179
|
+
# end
|
180
|
+
# end
|
165
181
|
def roll(&)
|
166
182
|
passed = 0
|
167
183
|
start = Time.now
|
@@ -185,6 +201,27 @@ class Fbe::Conclude
|
|
185
201
|
passed
|
186
202
|
end
|
187
203
|
|
204
|
+
# Populates a new fact based on a previous fact and a processing block.
|
205
|
+
#
|
206
|
+
# This internal method copies specified properties from the previous fact,
|
207
|
+
# calls the provided block for custom processing, and sets metadata
|
208
|
+
# on the new fact.
|
209
|
+
#
|
210
|
+
# @param [Factbase::Fact] fact The fact to populate
|
211
|
+
# @param [Factbase::Fact] prev The previous fact to copy from
|
212
|
+
# @yield [Factbase::Fact, Factbase::Fact] New fact and the previous fact
|
213
|
+
# @return [nil]
|
214
|
+
# @example
|
215
|
+
# # Inside the Fbe::Conclude class
|
216
|
+
# def example_method
|
217
|
+
# @fb.txn do |fbt|
|
218
|
+
# new_fact = fbt.insert
|
219
|
+
# fill(new_fact, existing_fact) do |n, prev|
|
220
|
+
# n.some_property = "new value"
|
221
|
+
# "Operation completed" # This becomes fact.details
|
222
|
+
# end
|
223
|
+
# end
|
224
|
+
# end
|
188
225
|
def fill(fact, prev)
|
189
226
|
@follows.each do |follow|
|
190
227
|
v = prev.send(follow)
|
data/lib/fbe/github_graph.rb
CHANGED
@@ -35,11 +35,29 @@ class Fbe::Graph
|
|
35
35
|
@host = host
|
36
36
|
end
|
37
37
|
|
38
|
+
# Executes a GraphQL query against the GitHub API.
|
39
|
+
#
|
40
|
+
# @param [String] qry The GraphQL query to execute
|
41
|
+
# @return [GraphQL::Client::Response] The query result data
|
42
|
+
# @example
|
43
|
+
# graph = Fbe::Graph.new(token: 'github_token')
|
44
|
+
# result = graph.query('{viewer {login}}')
|
45
|
+
# puts result.viewer.login #=> "octocat"
|
38
46
|
def query(qry)
|
39
47
|
result = client.query(client.parse(qry))
|
40
48
|
result.data
|
41
49
|
end
|
42
50
|
|
51
|
+
# Retrieves resolved conversation threads from a pull request.
|
52
|
+
#
|
53
|
+
# @param [String] owner The repository owner (username or organization)
|
54
|
+
# @param [String] name The repository name
|
55
|
+
# @param [Integer] number The pull request number
|
56
|
+
# @return [Array<Hash>] An array of resolved conversation threads with their comments
|
57
|
+
# @example
|
58
|
+
# graph = Fbe::Graph.new(token: 'github_token')
|
59
|
+
# threads = graph.resolved_conversations('octocat', 'Hello-World', 42)
|
60
|
+
# threads.first['comments']['nodes'].first['body'] #=> "Great work!"
|
43
61
|
def resolved_conversations(owner, name, number)
|
44
62
|
result = query(
|
45
63
|
<<~GRAPHQL
|
@@ -72,6 +90,16 @@ class Fbe::Graph
|
|
72
90
|
end || []
|
73
91
|
end
|
74
92
|
|
93
|
+
# Gets the total number of commits in a branch.
|
94
|
+
#
|
95
|
+
# @param [String] owner The repository owner (username or organization)
|
96
|
+
# @param [String] name The repository name
|
97
|
+
# @param [String] branch The branch name (e.g., "master" or "main")
|
98
|
+
# @return [Integer] The total number of commits in the branch
|
99
|
+
# @example
|
100
|
+
# graph = Fbe::Graph.new(token: 'github_token')
|
101
|
+
# count = graph.total_commits('octocat', 'Hello-World', 'main')
|
102
|
+
# puts count #=> 42
|
75
103
|
def total_commits(owner, name, branch)
|
76
104
|
result = query(
|
77
105
|
<<~GRAPHQL
|
@@ -93,6 +121,15 @@ class Fbe::Graph
|
|
93
121
|
result.repository.ref.target.history.total_count
|
94
122
|
end
|
95
123
|
|
124
|
+
# Gets the total number of issues and pull requests in a repository.
|
125
|
+
#
|
126
|
+
# @param [String] owner The repository owner (username or organization)
|
127
|
+
# @param [String] name The repository name
|
128
|
+
# @return [Hash] A hash with 'issues' and 'pulls' counts
|
129
|
+
# @example
|
130
|
+
# graph = Fbe::Graph.new(token: 'github_token')
|
131
|
+
# counts = graph.total_issues_and_pulls('octocat', 'Hello-World')
|
132
|
+
# puts counts #=> {"issues"=>42, "pulls"=>17}
|
96
133
|
def total_issues_and_pulls(owner, name)
|
97
134
|
result = query(
|
98
135
|
<<~GRAPHQL
|
@@ -116,6 +153,9 @@ class Fbe::Graph
|
|
116
153
|
|
117
154
|
private
|
118
155
|
|
156
|
+
# Creates or returns a cached GraphQL client instance.
|
157
|
+
#
|
158
|
+
# @return [GraphQL::Client] A configured GraphQL client for GitHub
|
119
159
|
def client
|
120
160
|
@client ||=
|
121
161
|
begin
|
@@ -127,13 +167,24 @@ class Fbe::Graph
|
|
127
167
|
end
|
128
168
|
end
|
129
169
|
|
130
|
-
#
|
170
|
+
# HTTP transport class for GraphQL client to communicate with GitHub API
|
171
|
+
#
|
172
|
+
# This class extends GraphQL::Client::HTTP to handle GitHub-specific
|
173
|
+
# authentication and endpoints.
|
131
174
|
class HTTP < GraphQL::Client::HTTP
|
175
|
+
# Initializes a new HTTP transport with GitHub authentication.
|
176
|
+
#
|
177
|
+
# @param [String] token GitHub API token for authentication
|
178
|
+
# @param [String] host GitHub API host (default: 'api.github.com')
|
132
179
|
def initialize(token, host)
|
133
180
|
@token = token
|
134
181
|
super("https://#{host}/graphql")
|
135
182
|
end
|
136
183
|
|
184
|
+
# Provides headers for GraphQL requests including authentication.
|
185
|
+
#
|
186
|
+
# @param [Object] _context The GraphQL request context (unused)
|
187
|
+
# @return [Hash] Headers for the request
|
137
188
|
def headers(_context)
|
138
189
|
{ Authorization: "Bearer #{@token}" }
|
139
190
|
end
|
@@ -54,11 +54,25 @@ class Fbe::Middleware::Formatter < Faraday::Logging::Formatter
|
|
54
54
|
|
55
55
|
private
|
56
56
|
|
57
|
+
# Indents text with two spaces, including all lines.
|
58
|
+
#
|
59
|
+
# @param [String, nil] txt The text to indent
|
60
|
+
# @return [String] The indented text, or an empty string if input was nil
|
61
|
+
# @example
|
62
|
+
# shifted("line1\nline2")
|
63
|
+
# #=> " line1\n line2"
|
57
64
|
def shifted(txt)
|
58
65
|
return '' if txt.nil?
|
59
66
|
" #{txt.gsub("\n", "\n ")}"
|
60
67
|
end
|
61
68
|
|
69
|
+
# Formats HTTP headers as a multi-line string.
|
70
|
+
#
|
71
|
+
# @param [Hash, nil] headers The headers to format
|
72
|
+
# @return [String] The formatted headers, or an empty string if input was nil
|
73
|
+
# @example
|
74
|
+
# dump_headers({"Content-Type" => "application/json", "Authorization" => "Bearer token"})
|
75
|
+
# #=> "Content-Type: \"application/json\"\nAuthorization: \"Bearer token\""
|
62
76
|
def dump_headers(headers)
|
63
77
|
return '' if headers.nil?
|
64
78
|
headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
|
data/lib/fbe/octo.rb
CHANGED
@@ -124,17 +124,40 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
124
124
|
end
|
125
125
|
end
|
126
126
|
|
127
|
-
# Fake GitHub client
|
127
|
+
# Fake GitHub client for testing purposes.
|
128
|
+
#
|
129
|
+
# This class provides mock implementations of Octokit methods for testing.
|
130
|
+
# It returns predictable data structures that mimic GitHub API responses.
|
128
131
|
class Fbe::FakeOctokit
|
132
|
+
# Generates a random time in the past.
|
133
|
+
#
|
134
|
+
# @return [Time] A random time within the last 10,000 seconds
|
135
|
+
# @example
|
136
|
+
# fake_client = Fbe::FakeOctokit.new
|
137
|
+
# time = fake_client.random_time #=> 2024-09-04 12:34:56 -0700
|
129
138
|
def random_time
|
130
139
|
Time.now - rand(10_000)
|
131
140
|
end
|
132
141
|
|
142
|
+
# Converts a string name to a deterministic integer.
|
143
|
+
#
|
144
|
+
# @param [String, Integer] name The name to convert or pass through
|
145
|
+
# @return [Integer, String] The sum of character codes if input is a string, otherwise the original input
|
146
|
+
# @example
|
147
|
+
# fake_client = Fbe::FakeOctokit.new
|
148
|
+
# fake_client.name_to_number("octocat") #=> 728
|
149
|
+
# fake_client.name_to_number(42) #=> 42
|
133
150
|
def name_to_number(name)
|
134
151
|
return name unless name.is_a?(String)
|
135
152
|
name.chars.sum(&:ord)
|
136
153
|
end
|
137
154
|
|
155
|
+
# Returns a mock rate limit object.
|
156
|
+
#
|
157
|
+
# @return [Object] An object with a remaining method that returns 100
|
158
|
+
# @example
|
159
|
+
# fake_client = Fbe::FakeOctokit.new
|
160
|
+
# fake_client.rate_limit.remaining #=> 100
|
138
161
|
def rate_limit
|
139
162
|
o = Object.new
|
140
163
|
def o.remaining
|
@@ -150,13 +173,25 @@ class Fbe::FakeOctokit
|
|
150
173
|
]
|
151
174
|
end
|
152
175
|
|
153
|
-
#
|
176
|
+
# Gives a star to a repository.
|
177
|
+
#
|
178
|
+
# @param [String] _repo The repository name (e.g., 'user/repo')
|
179
|
+
# @return [Boolean] Always returns true
|
180
|
+
# @example
|
181
|
+
# fake_client = Fbe::FakeOctokit.new
|
182
|
+
# fake_client.star('octocat/Hello-World') #=> true
|
154
183
|
def star(_repo)
|
155
184
|
true
|
156
185
|
end
|
157
186
|
|
158
|
-
#
|
159
|
-
#
|
187
|
+
# Gets details of a GitHub user.
|
188
|
+
#
|
189
|
+
# @param [String, Integer] uid The login of the user or its numeric ID
|
190
|
+
# @return [Hash] User information including id, login, and type
|
191
|
+
# @example
|
192
|
+
# fake_client = Fbe::FakeOctokit.new
|
193
|
+
# fake_client.user(526_301) #=> {:id=>444, :login=>"yegor256", :type=>"User"}
|
194
|
+
# fake_client.user('octocat') #=> {:id=>444, :login=>nil, :type=>"User"}
|
160
195
|
def user(uid)
|
161
196
|
login = (uid == 526_301 ? 'yegor256' : 'torvalds') if uid.is_a?(Integer)
|
162
197
|
{
|
@@ -166,6 +201,15 @@ class Fbe::FakeOctokit
|
|
166
201
|
}
|
167
202
|
end
|
168
203
|
|
204
|
+
# Gets workflow runs for a repository.
|
205
|
+
#
|
206
|
+
# @param [String] repo The repository name
|
207
|
+
# @param [Hash] _opts Additional options (not used in mock)
|
208
|
+
# @return [Hash] Information about workflow runs including counts and details
|
209
|
+
# @example
|
210
|
+
# fake_client = Fbe::FakeOctokit.new
|
211
|
+
# result = fake_client.repository_workflow_runs('octocat/Hello-World')
|
212
|
+
# result[:total_count] #=> 2
|
169
213
|
def repository_workflow_runs(repo, _opts = {})
|
170
214
|
{
|
171
215
|
total_count: 2,
|
@@ -176,6 +220,15 @@ class Fbe::FakeOctokit
|
|
176
220
|
}
|
177
221
|
end
|
178
222
|
|
223
|
+
# Gets usage information for a specific workflow run.
|
224
|
+
#
|
225
|
+
# @param [String] _repo The repository name
|
226
|
+
# @param [Integer] _id The workflow run ID
|
227
|
+
# @return [Hash] Billing and usage information for the workflow run
|
228
|
+
# @example
|
229
|
+
# fake_client = Fbe::FakeOctokit.new
|
230
|
+
# usage = fake_client.workflow_run_usage('octocat/Hello-World', 42)
|
231
|
+
# usage[:run_duration_ms] #=> 53000
|
179
232
|
def workflow_run_usage(_repo, _id)
|
180
233
|
{
|
181
234
|
billable: {
|
data/lib/fbe/pmp.rb
CHANGED
@@ -19,11 +19,27 @@ require_relative 'fb'
|
|
19
19
|
# this method throws an exception. The factbase must contain PMP-related facts.
|
20
20
|
# Most probably, a special judge must fill it up with such a fact.
|
21
21
|
#
|
22
|
+
# The method uses a double nested `others` block to create a chainable interface
|
23
|
+
# that allows accessing configuration like:
|
24
|
+
#
|
25
|
+
# Fbe.pmp.hr.reward_points
|
26
|
+
# Fbe.pmp.cost.hourly_rate
|
27
|
+
# Fbe.pmp.time.deadline
|
28
|
+
#
|
22
29
|
# @param [Factbase] fb The factbase
|
23
30
|
# @param [Hash] global The hash for global caching
|
24
31
|
# @param [Judges::Options] options The options coming from the +judges+ tool
|
25
32
|
# @param [Loog] loog The logging facility
|
26
|
-
# @return [
|
33
|
+
# @return [Object] A proxy object that allows method chaining to access PMP properties
|
34
|
+
# @example
|
35
|
+
# # Get HR reward points from PMP configuration
|
36
|
+
# points = Fbe.pmp.hr.reward_points
|
37
|
+
#
|
38
|
+
# # Get hourly rate from cost area
|
39
|
+
# rate = Fbe.pmp.cost.hourly_rate
|
40
|
+
#
|
41
|
+
# # Get deadline from time area
|
42
|
+
# deadline = Fbe.pmp.time.deadline
|
27
43
|
def Fbe.pmp(fb: Fbe.fb, global: $global, options: $options, loog: $loog)
|
28
44
|
others do |*args1|
|
29
45
|
area = args1.first
|
data/lib/fbe.rb
CHANGED
data/test/fbe/test_bylaws.rb
CHANGED
@@ -31,9 +31,9 @@ class TestBylaws < Fbe::Test
|
|
31
31
|
{ hoc: 30_000, contributors: 1 } => 32
|
32
32
|
},
|
33
33
|
'resolved-bug-was-rewarded' => {
|
34
|
-
{ hours: 1, self: 0 } =>
|
35
|
-
{ hours: 48, self: 0 } =>
|
36
|
-
{ hours: 80, self: 0 } =>
|
34
|
+
{ hours: 1, self: 0 } => 12,
|
35
|
+
{ hours: 48, self: 0 } => 6,
|
36
|
+
{ hours: 80, self: 0 } => 5,
|
37
37
|
{ hours: 300, self: 0 } => 4,
|
38
38
|
{ hours: 3_000, self: 0 } => 4,
|
39
39
|
{ hours: 30_000, self: 0 } => 4,
|
@@ -45,9 +45,10 @@ class TestBylaws < Fbe::Test
|
|
45
45
|
'code-review-was-rewarded' => {
|
46
46
|
{ hoc: 0, comments: 0, self: 0 } => 4,
|
47
47
|
{ hoc: 3, comments: 0, self: 0 } => 4,
|
48
|
-
{ hoc: 78, comments: 7, self: 0 } =>
|
49
|
-
{ hoc:
|
50
|
-
{ hoc:
|
48
|
+
{ hoc: 78, comments: 7, self: 0 } => 12,
|
49
|
+
{ hoc: 120, comments: 4, self: 0 } => 4,
|
50
|
+
{ hoc: 600, comments: 1, self: 0 } => 8,
|
51
|
+
{ hoc: 500, comments: 40, self: 0 } => 24,
|
51
52
|
{ hoc: 5_000, comments: 100, self: 0 } => 24,
|
52
53
|
{ hoc: 100, comments: 50, self: 1 } => 4,
|
53
54
|
{ hoc: 10_000, comments: 200, self: 1 } => 4
|
@@ -59,19 +60,19 @@ class TestBylaws < Fbe::Test
|
|
59
60
|
{ hoc: 78, comments: 1, reviews: 0 } => 4,
|
60
61
|
{ hoc: 50, comments: 15, reviews: 0 } => 4,
|
61
62
|
{ hoc: 50, comments: 25, reviews: 0 } => 4,
|
62
|
-
{ hoc: 180, comments: 7, reviews: 2 } =>
|
63
|
-
{ hoc: 199, comments: 8, reviews: 3 } =>
|
64
|
-
{ hoc: 150, comments: 5, reviews: 1 } =>
|
63
|
+
{ hoc: 180, comments: 7, reviews: 2 } => 24,
|
64
|
+
{ hoc: 199, comments: 8, reviews: 3 } => 24,
|
65
|
+
{ hoc: 150, comments: 5, reviews: 1 } => 24,
|
65
66
|
{ hoc: 500, comments: 25, reviews: 2 } => 4,
|
66
|
-
{ hoc: 99, comments: 6, reviews: 1 } =>
|
67
|
+
{ hoc: 99, comments: 6, reviews: 1 } => 16,
|
67
68
|
{ hoc: 1_500, comments: 3, reviews: 0 } => 4,
|
68
69
|
{ hoc: 15_000, comments: 40, reviews: 0 } => 4
|
69
70
|
},
|
70
71
|
'bug-report-was-rewarded' => {
|
71
|
-
{} =>
|
72
|
+
{} => 12
|
72
73
|
},
|
73
74
|
'enhancement-suggestion-was-rewarded' => {
|
74
|
-
{} =>
|
75
|
+
{} => 12
|
75
76
|
},
|
76
77
|
'dud-was-punished' => {
|
77
78
|
{} => -16
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fbe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: backtrace
|
@@ -253,8 +253,8 @@ email: yegor256@gmail.com
|
|
253
253
|
executables: []
|
254
254
|
extensions: []
|
255
255
|
extra_rdoc_files:
|
256
|
-
- README.md
|
257
256
|
- LICENSE.txt
|
257
|
+
- README.md
|
258
258
|
files:
|
259
259
|
- ".0pdd.yml"
|
260
260
|
- ".gitattributes"
|
@@ -358,7 +358,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
358
358
|
- !ruby/object:Gem::Version
|
359
359
|
version: '0'
|
360
360
|
requirements: []
|
361
|
-
rubygems_version: 3.6.
|
361
|
+
rubygems_version: 3.6.7
|
362
362
|
specification_version: 4
|
363
363
|
summary: FactBase Extended (FBE), a collection of utility classes for Zerocracy judges
|
364
364
|
test_files: []
|