Skip to content

Commit 8ed520b

Browse files
authored
Merge pull request #16 from joshdholtz/feature/github-api-rate-limiter
Add GitHub API rate limiter with search API support
2 parents a8d8e81 + 83057ca commit 8ed520b

File tree

6 files changed

+582
-50
lines changed

6 files changed

+582
-50
lines changed

bin/wassup

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ debug = ARGV.delete("--debug")
66
path = ARGV[0] || 'Supfile'
77
port = ARGV[1] || 0
88

9-
unless File.exists?(path)
9+
unless File.exist?(path)
1010
raise "Missing file: #{path}"
1111
end
1212

examples/github_api_demo.rb

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env ruby
2+
3+
require_relative '../lib/wassup/helpers/github'
4+
5+
# Example demonstrating how to use the new GitHub API method
6+
# to replace manual RestClient requests with rate-limited API calls
7+
8+
# Instead of manual RestClient calls like:
9+
# resp = RestClient::Request.execute(
10+
# method: :get,
11+
# url: "https://api.github.com/repos/owner/repo/pulls/123",
12+
# user: ENV["WASSUP_GITHUB_USERNAME"],
13+
# password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"]
14+
# )
15+
# pr = JSON.parse(resp)
16+
17+
# Use the new GitHub API method:
18+
org = "your-org"
19+
repo = "your-repo"
20+
pr_number = 123
21+
22+
# Get PR data
23+
pr = Wassup::Helpers::GitHub.api(path: "/repos/#{org}/#{repo}/pulls/#{pr_number}")
24+
25+
puts "PR: #{pr['title']}"
26+
puts "Draft: #{pr['draft']}"
27+
puts "Requested reviewers: #{pr['requested_reviewers'].size}"
28+
puts "Requested teams: #{pr['requested_teams'].size}"
29+
30+
# Get PR reviews
31+
reviews = Wassup::Helpers::GitHub.api(path: "/repos/#{org}/#{repo}/pulls/#{pr_number}/reviews")
32+
33+
# Filter out your own reviews
34+
reviews = reviews.select { |review| review["user"]["login"] != "your-username" }
35+
36+
approved = reviews.count { |review| review["state"] == "APPROVED" }
37+
changes_requested = reviews.count { |review| review["state"] == "CHANGES_REQUESTED" }
38+
39+
puts "Approved: #{approved}"
40+
puts "Changes requested: #{changes_requested}"
41+
42+
# Get check runs for the PR's head commit
43+
head_sha = pr["head"]["sha"]
44+
check_runs = Wassup::Helpers::GitHub.api(path: "/repos/#{org}/#{repo}/commits/#{head_sha}/check-runs")
45+
46+
puts "Check runs: #{check_runs['check_runs'].size}"
47+
48+
# Get commit statuses
49+
statuses = Wassup::Helpers::GitHub.api(path: "/repos/#{org}/#{repo}/commits/#{head_sha}/statuses")
50+
51+
puts "Statuses: #{statuses.size}"
52+
53+
# Count different status types
54+
success_count = statuses.count { |status| status["state"] == "success" }
55+
failure_count = statuses.count { |status| status["state"] == "failure" }
56+
57+
puts "Success: #{success_count}, Failures: #{failure_count}"
58+
59+
# Example with query parameters
60+
# Get PRs with specific state
61+
open_prs = Wassup::Helpers::GitHub.api(
62+
path: "/repos/#{org}/#{repo}/pulls",
63+
params: { state: "open", per_page: 10 }
64+
)
65+
66+
puts "Open PRs: #{open_prs.size}"
67+
68+
# Example with POST request (creating an issue comment)
69+
# comment_body = { body: "This is a test comment" }
70+
# new_comment = Wassup::Helpers::GitHub.api(
71+
# path: "/repos/#{org}/#{repo}/issues/#{pr_number}/comments",
72+
# method: :post,
73+
# body: comment_body
74+
# )

examples/rate_limiter_demo.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env ruby
2+
# Demo script to show how the rate limiter handles burst requests
3+
4+
require_relative '../lib/wassup/helpers/github_rate_limiter'
5+
6+
# Mock the RestClient for demo purposes
7+
class MockRestClient
8+
def self.execute(**args)
9+
# Simulate API response time
10+
sleep(0.05)
11+
12+
# Create a mock response with rate limit headers
13+
response = OpenStruct.new(
14+
body: '{"items": []}',
15+
headers: {
16+
x_ratelimit_remaining: rand(100..4999),
17+
x_ratelimit_reset: (Time.now + 3600).to_i,
18+
x_ratelimit_limit: 5000
19+
}
20+
)
21+
22+
puts "Request processed: #{args[:url]} (remaining: #{response.headers[:x_ratelimit_remaining]})"
23+
response.body
24+
end
25+
end
26+
27+
# Replace RestClient with our mock
28+
module RestClient
29+
Request = MockRestClient
30+
end
31+
32+
# Test burst handling
33+
puts "Testing Rate Limiter with Burst Requests"
34+
puts "=" * 50
35+
36+
rate_limiter = Wassup::Helpers::GitHub::RateLimiter.instance
37+
38+
# Send 20 requests simultaneously
39+
puts "\nSending 20 requests simultaneously..."
40+
start_time = Time.now
41+
42+
threads = []
43+
20.times do |i|
44+
threads << Thread.new do
45+
begin
46+
rate_limiter.execute_request(
47+
method: :get,
48+
url: "https://api.github.com/test/#{i}"
49+
)
50+
puts "Request #{i+1} completed"
51+
rescue => e
52+
puts "Request #{i+1} failed: #{e.message}"
53+
end
54+
end
55+
end
56+
57+
# Wait for all requests to complete
58+
threads.each(&:join)
59+
60+
end_time = Time.now
61+
puts "\nAll requests completed in #{(end_time - start_time).round(2)} seconds"
62+
63+
# Show final status
64+
puts "\nFinal Rate Limiter Status:"
65+
puts rate_limiter.status
66+
67+
# Cleanup
68+
rate_limiter.stop_worker

lib/wassup/helpers/github.rb

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module Helpers
33
module GitHub
44
require 'json'
55
require 'rest-client'
6+
require_relative 'github_rate_limiter'
67

78
# https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests
89
def self.issues(org:, repo:nil, q: nil)
@@ -22,12 +23,9 @@ def self.issues(org:, repo:nil, q: nil)
2223

2324
items = []
2425

25-
resp = RestClient::Request.execute(
26+
resp = RateLimiter.execute_request(
2627
method: :get,
27-
url: "https://api.github.com/search/issues?q=#{q}",
28-
user: ENV["WASSUP_GITHUB_USERNAME"],
29-
password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"],
30-
headers: { "Accept": "application/vnd.github.v3+json", "Content-Type": "application/json" },
28+
url: "https://api.github.com/search/issues?q=#{q}"
3129
)
3230
partial_items = JSON.parse(resp)["items"]
3331
items += partial_items
@@ -36,11 +34,9 @@ def self.issues(org:, repo:nil, q: nil)
3634
end
3735

3836
def self.repos(org:)
39-
resp = RestClient::Request.execute(
37+
resp = RateLimiter.execute_request(
4038
method: :get,
41-
url: "https://api.github.com/orgs/#{org}/repos",
42-
user: ENV["WASSUP_GITHUB_USERNAME"],
43-
password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"]
39+
url: "https://api.github.com/orgs/#{org}/repos"
4440
)
4541
return JSON.parse(resp)
4642
end
@@ -57,27 +53,56 @@ def self.pull_requests(org:, repo: nil)
5753
end
5854

5955
return repos.map do |repo|
60-
resp = RestClient::Request.execute(
56+
resp = RateLimiter.execute_request(
6157
method: :get,
62-
url: "https://api.github.com/repos/#{repo.org}/#{repo.repo}/pulls?per_page=100",
63-
user: ENV["WASSUP_GITHUB_USERNAME"],
64-
password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"]
58+
url: "https://api.github.com/repos/#{repo.org}/#{repo.repo}/pulls?per_page=100"
6559
)
6660

6761
JSON.parse(resp)
6862
end.flatten(1)
6963
end
7064

7165
def self.releases(org:, repo:)
72-
resp = RestClient::Request.execute(
66+
resp = RateLimiter.execute_request(
7367
method: :get,
74-
url: "https://api.github.com/repos/#{org}/#{repo}/releases",
75-
user: ENV["WASSUP_GITHUB_USERNAME"],
76-
password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"]
68+
url: "https://api.github.com/repos/#{org}/#{repo}/releases"
7769
)
7870

7971
return JSON.parse(resp)
8072
end
73+
74+
# Generic GitHub API method for any endpoint
75+
def self.api(path:, method: :get, params: {}, body: nil)
76+
# Handle full URLs or relative paths
77+
if path.start_with?('http')
78+
url = path
79+
else
80+
# Ensure path starts with /
81+
path = "/#{path}" unless path.start_with?('/')
82+
url = "https://api.github.com#{path}"
83+
end
84+
85+
# Add query parameters if provided
86+
if params.any?
87+
query_string = params.map { |k, v| "#{k}=#{v}" }.join('&')
88+
url += "?#{query_string}"
89+
end
90+
91+
# Prepare request options
92+
options = {}
93+
if body
94+
options[:payload] = body.is_a?(String) ? body : body.to_json
95+
end
96+
97+
# Make the request using the rate limiter
98+
resp = RateLimiter.execute_request(
99+
method: method,
100+
url: url,
101+
**options
102+
)
103+
104+
return JSON.parse(resp)
105+
end
81106
end
82107
end
83108
end

0 commit comments

Comments
 (0)