update-sponsors: don't require admin token.

Instead, use a different API to query these with a lower scope.

This should be usable by GitHub Actions.
This commit is contained in:
Mike McQuaid 2022-09-07 16:49:41 +01:00
parent 93ea8cb2c4
commit bc295f7947
No known key found for this signature in database
GPG Key ID: 3338A31AFDB1D829
5 changed files with 80 additions and 73 deletions

View File

@ -9,8 +9,8 @@ module Homebrew
module_function module_function
NAMED_TIER_AMOUNT = 100 NAMED_MONTHLY_AMOUNT = 100
URL_TIER_AMOUNT = 1000 URL_MONTHLY_AMOUNT = 1000
sig { returns(CLI::Parser) } sig { returns(CLI::Parser) }
def update_sponsors_args def update_sponsors_args
@ -23,16 +23,16 @@ module Homebrew
end end
end end
def sponsor_name(s) def sponsor_name(sponsor)
s["name"] || s["login"] sponsor[:name] || sponsor[:login]
end end
def sponsor_logo(s) def sponsor_logo(sponsor)
"https://github.com/#{s["login"]}.png?size=64" "https://github.com/#{sponsor[:login]}.png?size=64"
end end
def sponsor_url(s) def sponsor_url(sponsor)
"https://github.com/#{s["login"]}" "https://github.com/#{sponsor[:login]}"
end end
def update_sponsors def update_sponsors
@ -41,18 +41,13 @@ module Homebrew
named_sponsors = [] named_sponsors = []
logo_sponsors = [] logo_sponsors = []
GitHub.sponsors_by_tier("Homebrew").each do |tier| GitHub.sponsorships("Homebrew").each do |s|
if tier["tier"] >= NAMED_TIER_AMOUNT largest_monthly_amount = [s[:monthly_amount], s[:closest_tier_monthly_amount]].compact.max
named_sponsors += tier["sponsors"].map do |s| named_sponsors << "[#{sponsor_name(s)}](#{sponsor_url(s)})" if largest_monthly_amount >= NAMED_MONTHLY_AMOUNT
"[#{sponsor_name(s)}](#{sponsor_url(s)})"
end
end
next if tier["tier"] < URL_TIER_AMOUNT next if largest_monthly_amount < URL_MONTHLY_AMOUNT
logo_sponsors += tier["sponsors"].map do |s| logo_sponsors << "[![#{sponsor_name(s)}](#{sponsor_logo(s)})](#{sponsor_url(s)})"
"[![#{sponsor_name(s)}](#{sponsor_logo(s)})](#{sponsor_url(s)})"
end
end end
named_sponsors << "many other users and organisations via [GitHub Sponsors](https://github.com/sponsors/Homebrew)" named_sponsors << "many other users and organisations via [GitHub Sponsors](https://github.com/sponsors/Homebrew)"

View File

@ -57,14 +57,6 @@ describe GitHub do
end end
end end
describe "::sponsors_by_tier", :needs_network do
it "errors on an unauthenticated token" do
expect {
described_class.sponsors_by_tier("Homebrew")
}.to raise_error(/INSUFFICIENT_SCOPES|FORBIDDEN|token needs the 'admin:org' scope/)
end
end
describe "::get_artifact_url", :needs_network do describe "::get_artifact_url", :needs_network do
it "fails to find a nonexistent workflow" do it "fails to find a nonexistent workflow" do
expect { expect {

View File

@ -403,59 +403,75 @@ module GitHub
result["organization"]["team"]["members"]["nodes"].to_h { |member| [member["login"], member["name"]] } result["organization"]["team"]["members"]["nodes"].to_h { |member| [member["login"], member["name"]] }
end end
def sponsors_by_tier(user) def sponsorships(user)
query = <<~EOS has_next_page = true
{ organization(login: "#{user}") { after = ""
sponsorsListing { sponsorships = []
tiers(first: 10, orderBy: {field: MONTHLY_PRICE_IN_CENTS, direction: DESC}) { errors = []
while has_next_page
query = <<~EOS
{ organization(login: "#{user}") {
sponsorshipsAsMaintainer(first: 100 #{after}) {
pageInfo {
startCursor
hasNextPage
endCursor
}
totalCount
nodes { nodes {
monthlyPriceInDollars tier {
adminInfo { monthlyPriceInDollars
sponsorships(first: 100, includePrivate: true) { closestLesserValueTier {
totalCount monthlyPriceInDollars
nodes {
privacyLevel
sponsorEntity {
__typename
... on Organization { login name }
... on User { login name }
}
}
} }
} }
sponsorEntity {
__typename
... on Organization { login name }
... on User { login name }
}
} }
} }
} }
} }
} EOS
EOS # Some organisations do not permit themselves to be queried through the
result = API.open_graphql(query, scopes: ["admin:org", "user"]) # API like this and raise an error so handle these errors later.
# This has been reported to GitHub.
result = API.open_graphql(query, scopes: ["user"], raise_errors: false)
errors += result["errors"] if result["errors"].present?
tiers = result["organization"]["sponsorsListing"]["tiers"]["nodes"] current_sponsorships = result["data"]["organization"]["sponsorshipsAsMaintainer"]
tiers.map do |t| # The organisations mentioned above will show up as nil nodes.
tier = t["monthlyPriceInDollars"] sponsorships += current_sponsorships["nodes"].compact
raise API::Error, "Your token needs the 'admin:org' scope to access this API" if t["adminInfo"].nil?
sponsorships = t["adminInfo"]["sponsorships"] if (page_info = current_sponsorships["pageInfo"].presence) &&
count = sponsorships["totalCount"] page_info["hasNextPage"].presence
sponsors = sponsorships["nodes"].map do |sponsor| after = %Q(, after: "#{page_info["endCursor"]}")
next unless sponsor["privacyLevel"] == "PUBLIC" else
has_next_page = false
end
end
se = sponsor["sponsorEntity"] # Only raise errors if we didn't get any sponsorships.
{ if sponsorships.blank? && errors.present?
"name" => se["name"].presence || sponsor["login"], raise API::Error, errors.map { |e| "#{e["type"]}: #{e["message"]}" }.join("\n")
"login" => se["login"], end
"type" => se["__typename"].downcase,
} sponsorships.map do |sponsorship|
end.compact closest_tier_monthly_amount = sponsorship["tier"].fetch("closestLesserValueTier", nil)
&.fetch("monthlyPriceInDollars", nil)
monthly_amount = sponsorship["tier"]["monthlyPriceInDollars"]
sponsor = sponsorship["sponsorEntity"]
{ {
"tier" => tier, name: sponsor["name"].presence || sponsor["login"],
"count" => count, login: sponsor["login"],
"sponsors" => sponsors, monthly_amount: monthly_amount,
closest_tier_monthly_amount: closest_tier_monthly_amount || 0,
} }
end.compact end
end end
def get_repo_license(user, repo) def get_repo_license(user, repo)

View File

@ -252,17 +252,19 @@ module GitHub
end end
end end
def open_graphql(query, variables: nil, scopes: [].freeze) def open_graphql(query, variables: nil, scopes: [].freeze, raise_errors: true)
data = { query: query, variables: variables } data = { query: query, variables: variables }
result = open_rest("#{API_URL}/graphql", scopes: scopes, data: data, request_method: "POST") result = open_rest("#{API_URL}/graphql", scopes: scopes, data: data, request_method: "POST")
if result["errors"].present? if raise_errors
raise Error, result["errors"].map { |e| if result["errors"].present?
"#{e["type"]}: #{e["message"]}" raise Error, result["errors"].map { |e| "#{e["type"]}: #{e["message"]}" }.join("\n")
}.join("\n") end
end
result["data"] result["data"]
else
result
end
end end
def raise_error(output, errors, http_code, headers, scopes) def raise_error(output, errors, http_code, headers, scopes)

View File

@ -85,4 +85,6 @@ Flaky test detection and tracking is provided by [BuildPulse](https://buildpulse
[![DNSimple](https://cdn.dnsimple.com/assets/resolving-with-us/logo-light.png)](https://dnsimple.com/resolving/homebrew#gh-light-mode-only) [![DNSimple](https://cdn.dnsimple.com/assets/resolving-with-us/logo-light.png)](https://dnsimple.com/resolving/homebrew#gh-light-mode-only)
[![DNSimple](https://cdn.dnsimple.com/assets/resolving-with-us/logo-dark.png)](https://dnsimple.com/resolving/homebrew#gh-dark-mode-only) [![DNSimple](https://cdn.dnsimple.com/assets/resolving-with-us/logo-dark.png)](https://dnsimple.com/resolving/homebrew#gh-dark-mode-only)
Homebrew is generously supported by [Randy Reddig](https://github.com/ydnar), [Codecademy](https://github.com/Codecademy), [Appwrite](https://github.com/appwrite), [embark-studios](https://github.com/embark-studios) and many other users and organisations via [GitHub Sponsors](https://github.com/sponsors/Homebrew). Homebrew is generously supported by [GitHub](https://github.com/github), [Custom Ink](https://github.com/customink), [Randy Reddig](https://github.com/ydnar), [Christian (Xian) M. Lilley](https://github.com/xml), [David Fernandez](https://github.com/lidstromberg), [embark-studios](https://github.com/embark-studios) and many other users and organisations via [GitHub Sponsors](https://github.com/sponsors/Homebrew).
[![GitHub](https://github.com/github.png?size=64)](https://github.com/github)